<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged cicd</title>
<link>https://coderlegion.com/tag/cicd</link>
<description>Powered by Question2Answer</description>
<item>
<title>Segurança em CI/CD: a importância de estudar, auditar e aprender com pipelines de bons projetos</title>
<link>https://coderlegion.com/33090/seguranca-em-ci-cd-importancia-de-estudar-auditar-aprender-com-pipelines-de-bons-projetos</link>
<description>&lt;p&gt;Pipelines de CI/CD são frequentemente tratados como simples mecanismos de automação: executam testes, compilam aplicações, geram artefatos e realizam deploys. Entretanto, eles também representam uma das superfícies de ataque mais importantes de um ambiente de desenvolvimento moderno.&lt;/p&gt;
&lt;p&gt;Um pipeline possui acesso a repositórios, tokens, credenciais, registries, ambientes de produção, dependências externas e, em muitos casos, permissões para modificar recursos de infraestrutura. Isso significa que uma falha na configuração de um workflow pode ultrapassar os limites do processo de build e comprometer toda a cadeia de entrega de software.&lt;/p&gt;
&lt;p&gt;Por esse motivo, estudar a segurança de pipelines deve fazer parte da rotina de engenharia, assim como revisar código, analisar dependências e avaliar configurações de infraestrutura.&lt;/p&gt;
&lt;p&gt;Mais importante do que simplesmente executar ferramentas de análise é compreender por que determinada configuração representa um risco, em quais condições ela pode ser explorada e quais mecanismos realmente mitigam esse risco.&lt;/p&gt;
&lt;h3 id=&quot;1-por-que-estudar-pipelines-t-o-importante-&quot;&gt;1. Por que estudar pipelines é tão importante?&lt;/h3&gt;
&lt;p&gt;Um pipeline inseguro pode transformar uma alteração aparentemente simples em um vetor de comprometimento.&lt;/p&gt;
&lt;p&gt;Considere alguns exemplos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Um Pull Request pode introduzir código executado em um runner que possui acesso a credenciais.&lt;/li&gt;
&lt;li&gt;Uma interpolação insegura de contextos do GitHub pode permitir injeção de comandos no shell.&lt;/li&gt;
&lt;li&gt;Uma dependência comprometida pode executar código durante a instalação.&lt;/li&gt;
&lt;li&gt;Um token com permissões excessivas pode permitir alterações não autorizadas no repositório.&lt;/li&gt;
&lt;li&gt;Uma publicação sem mecanismos adequados de autorização pode disponibilizar uma versão não aprovada de um pacote.&lt;/li&gt;
&lt;li&gt;Um workflow mal configurado pode executar código de terceiros com privilégios superiores aos necessários.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Esses cenários demonstram que o pipeline não é apenas uma ferramenta operacional. Ele faz parte da infraestrutura de segurança da organização.&lt;/p&gt;
&lt;p&gt;A segurança precisa considerar todo o ciclo de execução: desde o evento que dispara o workflow até as permissões disponíveis, dependências instaladas, artefatos produzidos e operações realizadas.&lt;/p&gt;
&lt;h3 id=&quot;2-utilize-scanners-mas-n-o-terceirize-o-julgamento-t-cnico&quot;&gt;2. Utilize scanners, mas não terceirize o julgamento técnico&lt;/h3&gt;
&lt;p&gt;Existem ferramentas especializadas em identificar configurações inseguras em workflows.&lt;/p&gt;
&lt;p&gt;Algumas opções relevantes que eu utilizo no dia a dia para scans do pipeline:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;zizmor:&lt;/strong&gt; analisa workflows do GitHub Actions e identifica problemas como template injection, permissões inadequadas, uso inseguro de eventos e instalações de dependências fora de manifests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Checkov:&lt;/strong&gt; realiza análise estática de configurações de infraestrutura como código e também identifica problemas em configurações de CI/CD.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;actionlint:&lt;/strong&gt; valida a sintaxe, expressões, referências e possíveis problemas estruturais em workflows do GitHub Actions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenSSF Scorecard:&lt;/strong&gt; avalia práticas de segurança de projetos e repositórios, incluindo dependências, permissões, manutenção e configuração de workflows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trivy:&lt;/strong&gt; além de analisar imagens e dependências, consegue avaliar configurações e arquivos de infraestrutura.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Essas ferramentas são importantes porque automatizam a identificação de padrões conhecidos de risco.&lt;/p&gt;
&lt;p&gt;Entretanto, existe uma diferença fundamental entre encontrar uma configuração potencialmente insegura e demonstrar que ela é explorável.&lt;/p&gt;
&lt;p&gt;Um scanner identifica padrões. O engenheiro precisa compreender o contexto.&lt;/p&gt;
&lt;h4 id=&quot;exemplo-template-injection&quot;&gt;Exemplo: template injection&lt;/h4&gt;
&lt;p&gt;Considere:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Check changelog
  run: scripts/check-changelog-frozen.sh &quot;origin/${{ github.base_ref }}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O scanner pode identificar uma possível injeção de comandos porque o contexto é interpolado diretamente no código executado pelo shell.&lt;/p&gt;
&lt;p&gt;O risco existe porque o GitHub Actions realiza a expansão da expressão antes de executar o script.&lt;/p&gt;
&lt;p&gt;Entretanto, é necessário entender a origem do dado.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;github.base_ref&lt;/code&gt; representa a branch de destino do Pull Request, enquanto &lt;code&gt;github.head_ref&lt;/code&gt; representa a branch de origem.&lt;/p&gt;
&lt;p&gt;Um colaborador externo normalmente controla o nome da própria branch, mas não controla livremente a branch de destino.&lt;/p&gt;
&lt;p&gt;Portanto, embora exista um padrão vulnerável, a explorabilidade depende das permissões e das condições do workflow.&lt;/p&gt;
&lt;p&gt;A correção recomendada é transportar o valor por uma variável de ambiente:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Check changelog
  env:
    BASE_REF: ${{ github.base_ref }}
  run: scripts/check-changelog-frozen.sh &quot;origin/$BASE_REF&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;O valor passa a ser tratado como dado, em vez de ser inserido diretamente no código interpretado pelo shell.&lt;/p&gt;
&lt;p&gt;Esse exemplo demonstra por que é necessário estudar os findings: o scanner identifica corretamente o padrão, mas a análise de impacto exige conhecimento sobre os contextos do GitHub Actions.&lt;/p&gt;
&lt;h3 id=&quot;3-aprenda-a-diferenciar-vulnerabilidades-reais-de-findings-parcialmente-mitigados&quot;&gt;3. Aprenda a diferenciar vulnerabilidades reais de findings parcialmente mitigados&lt;/h3&gt;
&lt;p&gt;Nem todo alerta representa uma vulnerabilidade diretamente explorável.&lt;/p&gt;
&lt;p&gt;Um exemplo interessante é a instalação de dependências durante o pipeline.&lt;/p&gt;
&lt;p&gt;Considere:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;env:
  CLAUDE_CODE_VERSION: &quot;2.1.296&quot;
  CODEX_VERSION: &quot;0.162.1&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;E posteriormente:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Install CLIs
  run: |
    npm install --global \
      &quot;@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}&quot; \
      &quot;@openai/codex@${CODEX_VERSION}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Um scanner como o zizmor pode identificar o padrão &lt;code&gt;adhoc-packages&lt;/code&gt;, pois os pacotes são instalados globalmente sem um manifesto e lockfile controlando a instalação.&lt;/p&gt;
&lt;p&gt;Entretanto, existe uma particularidade: as versões principais já estão explicitamente fixadas.&lt;/p&gt;
&lt;p&gt;Isso significa que o workflow não está simplesmente instalando a última versão disponível a cada execução.&lt;/p&gt;
&lt;p&gt;Ainda existe uma fragilidade: a árvore completa de dependências não está necessariamente reproduzível, pois as dependências transitivas não são controladas por um lockfile versionado.&lt;/p&gt;
&lt;p&gt;É importante distinguir:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Versão principal fixada: controla qual versão direta será solicitada.&lt;/li&gt;
&lt;li&gt;Manifesto: declara as dependências do projeto.&lt;/li&gt;
&lt;li&gt;Lockfile: registra a resolução das dependências e suas integridades.&lt;/li&gt;
&lt;li&gt;Instalação reproduzível: utiliza a resolução registrada para reconstruir o ambiente.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A ausência de lockfile continua sendo um finding válido, mas não significa automaticamente que existe uma instalação de versão flutuante ou que um pacote esteja comprometido.&lt;/p&gt;
&lt;p&gt;O objetivo da análise não é eliminar alertas indiscriminadamente, mas determinar quais riscos permanecem e quais controles já existem.&lt;/p&gt;
&lt;h3 id=&quot;4-estude-como-projetos-maduros-resolvem-problemas-de-seguran-a&quot;&gt;4. Estude como projetos maduros resolvem problemas de segurança&lt;/h3&gt;
&lt;p&gt;Uma das formas mais eficientes de evoluir na segurança de pipelines é estudar workflows de projetos open source bem mantidos.&lt;/p&gt;
&lt;p&gt;Em vez de começar sempre do zero, investigue como projetos com grande exposição pública estruturam seus processos de CI/CD.&lt;/p&gt;
&lt;p&gt;Alguns exemplos interessantes para estudo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes:&lt;/strong&gt; organização de workflows, testes, automação e governança de contribuições.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Terraform:&lt;/strong&gt; validação, testes, releases e publicação de artefatos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trivy:&lt;/strong&gt; análise de segurança, integração de scanners e automação de releases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenTelemetry:&lt;/strong&gt; testes distribuídos, validação de componentes e publicação de versões.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;actions/checkout:&lt;/strong&gt; implementação e manutenção de GitHub Actions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sigstore:&lt;/strong&gt; assinatura, verificação e estabelecimento de confiança em artefatos.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ao estudar esses projetos, não observe apenas quais actions são utilizadas. Investigue as decisões arquiteturais.&lt;/p&gt;
&lt;p&gt;Pergunte:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Como o workflow é disparado?&lt;/li&gt;
&lt;li&gt;Ele aceita Pull Requests de forks?&lt;/li&gt;
&lt;li&gt;Quais permissões são concedidas ao &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Existem secrets disponíveis durante a execução?&lt;/li&gt;
&lt;li&gt;Como as dependências são instaladas?&lt;/li&gt;
&lt;li&gt;As GitHub Actions são referenciadas por tags ou hashes SHA completos?&lt;/li&gt;
&lt;li&gt;Como os artefatos são assinados e verificados?&lt;/li&gt;
&lt;li&gt;Como as publicações são autorizadas?&lt;/li&gt;
&lt;li&gt;Existem ambientes protegidos e aprovações humanas?&lt;/li&gt;
&lt;li&gt;Como o projeto separa testes, validação e publicação?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;O objetivo é compreender o motivo de cada decisão, e não simplesmente copiar configurações.&lt;/p&gt;
&lt;p&gt;Uma configuração segura em determinado contexto pode ser inadequada em outro.&lt;/p&gt;
&lt;h3 id=&quot;5-construa-uma-rotina-de-auditoria-de-pipelines&quot;&gt;5. Construa uma rotina de auditoria de pipelines&lt;/h3&gt;
&lt;p&gt;A análise não deve acontecer somente quando um scanner encontra um problema.&lt;/p&gt;
&lt;p&gt;Uma rotina de auditoria pode ser incorporada ao próprio processo de engenharia.&lt;/p&gt;
&lt;p&gt;Uma abordagem prática:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Etapa 1 — Inventário&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Mapear todos os workflows existentes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CI.&lt;/li&gt;
&lt;li&gt;CD.&lt;/li&gt;
&lt;li&gt;Release.&lt;/li&gt;
&lt;li&gt;Publicação de pacotes.&lt;/li&gt;
&lt;li&gt;Provisionamento de infraestrutura.&lt;/li&gt;
&lt;li&gt;Automação de dependências.&lt;/li&gt;
&lt;li&gt;Segurança e compliance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Etapa 2 — Análise automatizada&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Executar ferramentas como:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;zizmor .github/workflows
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;actionlint
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;checkov -d .github/workflows
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;scorecard --repo=github.com/ORG/REPOSITORY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Os comandos devem ser ajustados à versão e à forma de instalação de cada ferramenta.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Etapa 3 — Investigação dos findings&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Para cada alerta:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Identificar o trecho responsável.&lt;/li&gt;
&lt;li&gt;Entender a regra acionada.&lt;/li&gt;
&lt;li&gt;Identificar a origem dos dados.&lt;/li&gt;
&lt;li&gt;Verificar se existe controle compensatório.&lt;/li&gt;
&lt;li&gt;Avaliar a possibilidade de exploração.&lt;/li&gt;
&lt;li&gt;Determinar o impacto potencial.&lt;/li&gt;
&lt;li&gt;Definir a correção ou justificar uma exceção.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Etapa 4 — Correção&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Priorizar problemas que permitam execução de código não confiável, exposição de credenciais, elevação de privilégios e comprometimento de releases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Etapa 5 — Prevenção&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Incorporar os scanners ao próprio CI para impedir que configurações inseguras sejam introduzidas novamente.&lt;/p&gt;
&lt;h3 id=&quot;6-crie-um-laborat-rio-utilizando-projetos-reais&quot;&gt;6. Crie um laboratório utilizando projetos reais&lt;/h3&gt;
&lt;p&gt;Uma estratégia especialmente interessante é escolher projetos open source e realizar auditorias independentes em seus workflows.&lt;/p&gt;
&lt;p&gt;Não é necessário encontrar uma vulnerabilidade para que o estudo seja útil.&lt;/p&gt;
&lt;p&gt;O exercício consiste em:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Selecionar um repositório ativo e bem mantido.&lt;/li&gt;
&lt;li&gt;Baixar ou clonar seus workflows.&lt;/li&gt;
&lt;li&gt;Executar os scanners.&lt;/li&gt;
&lt;li&gt;Investigar os findings.&lt;/li&gt;
&lt;li&gt;Consultar a documentação das regras acionadas.&lt;/li&gt;
&lt;li&gt;Comparar a implementação com as recomendações oficiais.&lt;/li&gt;
&lt;li&gt;Identificar controles compensatórios.&lt;/li&gt;
&lt;li&gt;Propor correções.&lt;/li&gt;
&lt;li&gt;Registrar os resultados em um pequeno relatório técnico.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;É possível inclusive comparar diferentes projetos para entender como cada equipe resolve o mesmo problema.&lt;/p&gt;
&lt;p&gt;Por exemplo: como diferentes projetos implementam Trusted Publishing, como controlam dependências de terceiros ou como protegem workflows executados por Pull Requests externos.&lt;/p&gt;
&lt;p&gt;Esse exercício desenvolve uma habilidade importante: sair da posição de alguém que apenas corrige alertas e passar a compreender a arquitetura de segurança do processo de entrega.&lt;/p&gt;
&lt;p&gt;Segurança de CI/CD exige conhecimento sobre GitHub Actions, shell, permissões, autenticação, dependências, infraestrutura e supply chain security.&lt;/p&gt;
&lt;p&gt;É justamente por isso que estudar workflows de projetos maduros é tão valioso: eles oferecem exemplos concretos de decisões de engenharia, problemas reais e estratégias de mitigação que podem ser adaptadas a outros ambientes.&lt;/p&gt;
&lt;h3 id=&quot;conclus-o&quot;&gt;Conclusão&lt;/h3&gt;
&lt;p&gt;Scanners são instrumentos de diagnóstico, não substitutos do conhecimento técnico.&lt;/p&gt;
&lt;p&gt;Executar um scanner e aplicar todas as correções sugeridas sem entender os findings pode gerar configurações desnecessárias, exceções mal justificadas e até novos problemas.&lt;/p&gt;
&lt;p&gt;O caminho mais consistente é combinar análise automatizada, estudo da documentação, investigação técnica e comparação com implementações de projetos reconhecidos.&lt;/p&gt;
&lt;p&gt;Estudar pipelines de bons projetos é uma forma de aprender com decisões de engenharia que já foram colocadas à prova.&lt;/p&gt;
&lt;p&gt;Afinal, um pipeline seguro não é aquele que simplesmente passa em todas as verificações. É aquele cuja arquitetura foi compreendida, cujos riscos são conhecidos e cujos mecanismos de proteção foram implementados de maneira consciente.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33090/seguranca-em-ci-cd-importancia-de-estudar-auditar-aprender-com-pipelines-de-bons-projetos</guid>
<pubDate>Fri, 30 Oct 2026 12:10:32 +0000</pubDate>
</item>
<item>
<title>Flyway Migration: From Legacy Database to CI/CD</title>
<link>https://coderlegion.com/32961/flyway-migration-from-legacy-database-to-ci-cd</link>
<description>&lt;p&gt;Most teams think of Flyway only when they need to manage ongoing database schema changes. But what if your project already has a complete, stable schema and you’re only now introducing version control? Or what if you want to incorporate your schema into a CI/CD pipeline for reliable, reproducible deployments?&lt;/p&gt;
&lt;p&gt;In this guide, I’ll walk you through a real-world scenario where we used Flyway to baseline an existing MySQL database and then extended it to support version-controlled schema migrations in CI/CD, without disrupting production.&lt;/p&gt;
&lt;h3 id=&quot;what-is-flyway-&quot;&gt;What is Flyway?&lt;/h3&gt;
&lt;p&gt;Flyway is a simple but powerful tool for managing database migrations. It applies versioned SQL scripts to your database and tracks them in a special table called &lt;code&gt;flyway_schema_history&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Typical script names:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;V1__initial_schema.sql
V2__add_orders_table.sql
V3__rename_column.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Flyway ensures these scripts run only once and in the correct order. It also integrates well with CI/CD tools, making it ideal for automated environments.&lt;/p&gt;
&lt;h3 id=&quot;use-case-existing-schema-with-no-migrations-yet-&quot;&gt;Use Case: Existing Schema with No Migrations (Yet)&lt;/h3&gt;
&lt;p&gt;In our project, we had:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A production MySQL database with dozens of tables&lt;/li&gt;
&lt;li&gt;No migration history&lt;/li&gt;
&lt;li&gt;No current need for structural changes&lt;/li&gt;
&lt;li&gt;A desire to version control the database and enable future migrations via CI/CD&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;step-1-baselining-an-existing-schema&quot;&gt;Step 1: Baselining an Existing Schema&lt;/h3&gt;
&lt;h4 id=&quot;dump-the-schema&quot;&gt;Dump the schema&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mysqldump -u root -p --no-data your_database &amp;gt; V1__baseline_schema.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clean the output: remove auto-increments, user references, and environment-specific SQL.&lt;/p&gt;
&lt;h4 id=&quot;create-directory-structure&quot;&gt;Create directory structure&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p db/sql
mv V1__baseline_schema.sql db/sql/
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;configure-flyway-conf&quot;&gt;Configure flyway.conf&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;flyway.url=jdbc:mysql://your-db-host:3306/your_database
flyway.user=db_user
flyway.password=secret_password
flyway.locations=filesystem:db/sql
flyway.baselineOnMigrate=true
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;run-baseline&quot;&gt;Run baseline&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;flyway baseline
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates the &lt;code&gt;flyway_schema_history&lt;/code&gt; table and marks version 1 as the current state without running any script.&lt;/p&gt;
&lt;h3 id=&quot;step-2-add-a-real-migration&quot;&gt;Step 2: Add a Real Migration&lt;/h3&gt;
&lt;p&gt;After baselining, we added a real change in a new script&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- db/sql/V2__add_status_to_orders.sql
ALTER TABLE orders ADD COLUMN status VARCHAR(20) DEFAULT 'pending';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we ran:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;flyway migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Flyway applied version 2, recorded it in history, and our database was now safely versioned.&lt;/p&gt;
&lt;h3 id=&quot;-step-3-integrate-flyway-into-ci-cd&quot;&gt;⚙️ Step 3: Integrate Flyway into CI/CD&lt;/h3&gt;
&lt;p&gt;You can integrate Flyway with GitHub Actions, GitLab CI, Bitbucket Pipelines, or Jenkins.&lt;/p&gt;
&lt;h3 id=&quot;-example-github-actions-workflow&quot;&gt;✅ Example GitHub Actions Workflow&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Flyway Migration
on:
  push:
    branches: [ main ]

jobs:
  migrate:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3
      - name: Set up Java (Flyway needs it)
        uses: actions/setup-java@v3
        with:
          distribution: 'temurin'
          java-version: '17'
      - name: Install Flyway
        run: |
          curl -L https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/10.8.1/flyway-commandline-10.8.1-linux-x64.tar.gz | tar xvz
          sudo ln -s $PWD/flyway-10.8.1/flyway /usr/local/bin/flyway
      - name: Run Flyway Migrations
        env:
          FLYWAY_URL: jdbc:mysql://your-host:3306/your_database
          FLYWAY_USER: ${{ secrets.DB_USER }}
          FLYWAY_PASSWORD: ${{ secrets.DB_PASSWORD }}
        run: |
          flyway -url=$FLYWAY_URL \
                 -user=$FLYWAY_USER \
                 -password=$FLYWAY_PASSWORD \
                 -locations=filesystem:./db/sql \
                 migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;benefits-of-this-approach&quot;&gt;Benefits of This Approach&lt;/h3&gt;
&lt;p&gt;✅ You gain &lt;strong&gt;auditability&lt;/strong&gt; for all database changes&lt;/p&gt;
&lt;p&gt;✅ Developers can build &lt;strong&gt;local environments&lt;/strong&gt; using the same scripts&lt;/p&gt;
&lt;p&gt;✅ CI/CD ensures &lt;strong&gt;consistency&lt;/strong&gt; across all environments&lt;/p&gt;
&lt;p&gt;✅ Flyway’s &lt;code&gt;validate&lt;/code&gt; command catches missing or changed scripts&lt;/p&gt;
&lt;p&gt;✅ Schema management becomes part of your &lt;strong&gt;software delivery lifecycle&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;potential-challenges&quot;&gt;Potential Challenges&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem → Fix&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Flyway re-applies the baseline script&lt;br&gt;
→ Use baselineOnMigrate=true and never modify a baseline&lt;/li&gt;
&lt;li&gt;Passwords in config files&lt;br&gt;
→ Use secrets and environment variables in CI/CD&lt;/li&gt;
&lt;li&gt;Manual schema drift&lt;br&gt;
→ Disable direct DB access in prod or run flyway validate periodically&lt;/li&gt;
&lt;li&gt;Need rollback support&lt;br&gt;
→ Write manual undo scripts or use point-in-time backups&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;alternatives-and-why-flyway-wins-&quot;&gt;Alternatives (and Why Flyway Wins)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt; Liquibase&lt;/strong&gt; &lt;br&gt;
✅ Pros: Advanced features, rollback support &lt;br&gt;
❌ Cons: More complex, steeper learning curve&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Django ORM / Alembic&lt;/strong&gt; &lt;br&gt;
✅ Pros: Built-in for app devs &lt;br&gt;
❌ Cons: Not cross-language or DB-agnostic&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Manual SQL or DB dumps&lt;/strong&gt; &lt;br&gt;
✅ Pros: Simple &lt;br&gt;
❌ Cons: No versioning, no tracking, no automation&lt;/p&gt;
&lt;p&gt;Flyway is a great middle-ground: powerful enough for teams, simple enough for solo devs, and perfect for CI/CD.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Flyway isn’t just for teams writing migrations every day. It’s also ideal for bringing an existing database under version control, giving your project a foundation for safe schema evolution.&lt;/p&gt;
&lt;p&gt;We started with a baseline, added our first real migration, and then built it into our CI/CD pipeline — all in a day’s work.&lt;/p&gt;
&lt;p&gt;If you’re managing an existing DB without schema tracking, now’s the time to fix that. Start with a baseline. Your future self will thank you.&lt;/p&gt;
&lt;h3 id=&quot;let-s-connect-&quot;&gt;Let’s Connect!&lt;/h3&gt;
&lt;p&gt;Have you tried Flyway for baseline-only use cases or integrated it into your CI/CD pipeline? I’d love to hear your experience in the comments.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you found this helpful, follow me here on DEV for more DevOps and database engineering content.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32961/flyway-migration-from-legacy-database-to-ci-cd</guid>
<pubDate>Thu, 29 Oct 2026 12:05:30 +0000</pubDate>
</item>
<item>
<title>⚙️From 4 Hours to 3 Minutes: Automating GCP Ingress Provisioning with Ansible</title>
<link>https://coderlegion.com/32728/from-4-hours-to-3-minutes-automating-gcp-ingress-provisioning-with-ansible</link>
<description>&lt;h4 id=&quot;context&quot;&gt;Context&lt;/h4&gt;
&lt;p&gt;Every time a new client was onboarded, their web application had to be deployed and exposed to the internet.&lt;/p&gt;
&lt;p&gt;As part of the deployment, the infrastructure required several configuration steps across different services before the application was ready to receive traffic.&lt;/p&gt;
&lt;h4 id=&quot;problem&quot;&gt;Problem&lt;/h4&gt;
&lt;p&gt;Although the deployment workflow was always the same, every new client required repeating the exact same operational tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creating GCP firewall rules&lt;/li&gt;
&lt;li&gt;Retrieving the VM's public IP&lt;/li&gt;
&lt;li&gt;Creating or updating Cloudflare DNS records&lt;/li&gt;
&lt;li&gt;Installing and configuring Nginx&lt;/li&gt;
&lt;li&gt;Generating Let's Encrypt SSL certificates&lt;/li&gt;
&lt;li&gt;Validating the routing and HTTPS configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since these tasks were performed manually for every client, the provisioning process typically took around four hours to complete. Besides being time-consuming, it also increased the likelihood of configuration mistakes and inconsistencies.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;solution&quot;&gt;Solution&lt;/h4&gt;
&lt;p&gt;After repeating the same workflow several times, I realized the process was entirely deterministic. The only inputs that changed between deployments were values such as the domain name, backend ports, and service account.&lt;/p&gt;
&lt;p&gt;Instead of spending hours repeating the same sequence of tasks, I automated the entire workflow with Ansible and integrated it into our CI/CD pipeline.&lt;/p&gt;
&lt;h4 id=&quot;how-the-workflow-looks&quot;&gt;How the workflow looks&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;GitLab CI/CD
      │
      ▼
Run Ansible Playbook
      │
      ▼
Create GCP Firewall Rule
      │
      ▼
Retrieve VM Public IP
      │
      ▼
Create / Update Cloudflare DNS Record
      │
      ▼
Install Nginx
      │
      ▼
Generate HTTP Configuration
      │
      ▼
Request Let's Encrypt Certificate
      │
      ▼
Generate HTTPS Configuration
      │
      ▼
Reload Nginx
      │
      ▼
Application Available via HTTPS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A single CI/CD job orchestrates the entire workflow. Ansible handles the infrastructure configuration, DNS updates, reverse proxy setup, and SSL provisioning, making the deployment consistent and repeatable.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;implementation-highlights&quot;&gt;Implementation Highlights&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Google Cloud Firewall Automation – Creates HTTP/HTTPS firewall rules through the GCP API.&lt;/li&gt;
&lt;li&gt;Automatic Public IP Discovery – Retrieves the VM's external IP from the GCP metadata service instead of requiring manual input.&lt;/li&gt;
&lt;li&gt;Cloudflare DNS Management – Updates DNS records automatically as part of the deployment.&lt;/li&gt;
&lt;li&gt;Dynamic Nginx Configuration – Uses Jinja2 templates to generate HTTP and HTTPS configurations.&lt;/li&gt;
&lt;li&gt;Automated SSL Provisioning – Obtains and configures Let's Encrypt certificates without manual intervention.&lt;/li&gt;
&lt;li&gt;CI/CD Integration – The entire process runs automatically through GitLab CI.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;results&quot;&gt;Results&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Before                                     &lt;/th&gt;&lt;th&gt;After&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;~4 hours to provision and configure        &lt;/td&gt;&lt;td&gt;~3 minutes through CI/CD&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Multiple manual steps                      &lt;/td&gt;&lt;td&gt;Fully automated workflow&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Manual DNS, firewall and SSL configuration &lt;/td&gt;&lt;td&gt;Managed by Ansible&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Repetitive onboarding process              &lt;/td&gt;&lt;td&gt;Consistent and repeatable deployment&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Beyond the time savings, every deployment now follows the exact same process.&lt;/p&gt;
&lt;p&gt;Instead of spending hours repeating the same steps, I simply trigger the pipeline and let the automation do the work.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;check-the-code&quot;&gt;Check the code&lt;/h4&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ja-lourenco/ansible-gcp-ingress-proxy&quot;&gt;https://github.com/ja-lourenco/ansible-gcp-ingress-proxy&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32728/from-4-hours-to-3-minutes-automating-gcp-ingress-provisioning-with-ansible</guid>
<pubDate>Tue, 27 Oct 2026 16:36:09 +0000</pubDate>
</item>
<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>Evaluating and Testing AI Agents in Production: How to Measure the Unpredictable</title>
<link>https://coderlegion.com/29943/evaluating-and-testing-ai-agents-in-production-how-to-measure-the-unpredictable</link>
<description>&lt;p&gt;In conventional software development, the boundary between success and failure is binary, deterministic, and unambiguous. You write a discrete function, formulate a unit test with &lt;code&gt;assert calculate_discount(100, 0.2) == 80&lt;/code&gt;, and when your continuous integration pipeline turns green, you ship to production with absolute confidence.&lt;/p&gt;
&lt;p&gt;Yet when building an &lt;strong&gt;Autonomous AI Agent&lt;/strong&gt;, that entire bedrock of certainty dissolves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User inputs are unstructured, open-ended natural language prompts.&lt;/li&gt;
&lt;li&gt;The underlying foundation model is fundamentally stochastic (probabilistic) by mathematical design.&lt;/li&gt;
&lt;li&gt;The agent dynamically orchestrates multi-step &lt;strong&gt;Tool Calling&lt;/strong&gt;, executes SQL queries, traverses external APIs, and autonomously decides how many intermediate reasoning cycles it requires to fulfill its objective.&lt;/li&gt;
&lt;li&gt;Most perilously: &lt;strong&gt;a minor adjustment to a single phrase in your system prompt designed to improve conversational tone can silently cause the agent to omit a mandatory currency parameter in a Stripe API call or trigger an infinite retry loop in an unrelated workflow&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In 2026, following our operational deep dives into &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/mlops_for_engineers/&quot;&gt;MLOps for Engineers&lt;/a&gt;, execution vulnerabilities in &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/prompt_injection/&quot;&gt;Prompt Injection&lt;/a&gt;, and autonomous orchestration across our &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/ai_agents_part9/&quot;&gt;Autopilot series&lt;/a&gt;, the software engineering industry has encountered an unavoidable truth: &lt;strong&gt;over 80% of enterprise AI agent initiatives stall when attempting to cross the chasm separating a flashy prototype in a Jupyter notebook from an enterprise-grade production environment&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The root cause is not an intellectual deficit in frontier reasoning engines like &lt;strong&gt;Gemini 3.8&lt;/strong&gt;, &lt;strong&gt;Claude Fable 5.1&lt;/strong&gt;, or &lt;strong&gt;GPT Sol 5.6&lt;/strong&gt;. The root cause is the &lt;strong&gt;absence of a rigorous, repeatable discipline of continuous evaluation and automated testing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;How do we systematically test that which is non-deterministic by architecture?&lt;/p&gt;
&lt;h4 id=&quot;the-collapse-of-legacy-metrics-and-the-peril-of-vibe-checking-&quot;&gt;The Collapse of Legacy Metrics and the Peril of 'Vibe Checking'&lt;/h4&gt;
&lt;p&gt;During the formative years of generative AI adoption, engineering teams leaned on methodological shortcuts that are today recognized as technically inadequate:&lt;/p&gt;
&lt;h5 id=&quot;1-classical-nlp-metrics-bleu-rouge-meteor-&quot;&gt;1. Classical NLP Metrics (BLEU, ROUGE, METEOR)&lt;/h5&gt;
&lt;p&gt;Formulated decades ago for machine translation and extractive summarization, these metrics measure raw n-gram surface overlap between generated text and a static reference string. In agentic workflows, they are blind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An agent can deliver an answer using entirely disparate vocabulary while being technically, mathematically, and logically flawless.&lt;/li&gt;
&lt;li&gt;Conversely, an agent can achieve a 95% word match against a reference answer while introducing a catastrophic numerical error (&lt;em&gt;“Flight departs at 14:00”&lt;/em&gt; vs. &lt;em&gt;“Flight departs at 04:00”&lt;/em&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;2-embedding-cosine-similarity&quot;&gt;2. Embedding Cosine Similarity&lt;/h5&gt;
&lt;p&gt;Evaluating accuracy by measuring semantic distance in vector space (as examined in &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/graphrag/&quot;&gt;GraphRAG&lt;/a&gt;) represents a dangerous engineering trap. The propositions &lt;em&gt;“The agreement has been formally signed and approved”&lt;/em&gt; and &lt;em&gt;“The agreement has not been signed or approved”&lt;/em&gt; share a cosine proximity exceeding &lt;strong&gt;0.93&lt;/strong&gt;, despite possessing diametrically opposed legal, operational, and financial realities.&lt;/p&gt;
&lt;h5 id=&quot;3-vibe-checking-&quot;&gt;3. 'Vibe Checking'&lt;/h5&gt;
&lt;p&gt;The most pervasive anti-pattern in modern software engineering: a developer manually runs three prompts in a terminal, skims the conversational responses, nods approvingly because the prose “sounds convincing,” and approves the Pull Request. This practice is the direct equivalent of shipping a core banking engine without unit tests simply because the ATM screen illuminated upon being plugged into the wall.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/evaluating_ai_agents/agent_evaluation_framework.jpg&quot; alt=&quot;Comprehensive 4-layer evaluation framework for autonomous AI agents in production&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;the-4-layer-evaluation-framework-for-ai-agents&quot;&gt;The 4-Layer Evaluation Framework for AI Agents&lt;/h4&gt;
&lt;p&gt;To guarantee operational reliability in production, an agent's execution must be deconstructed across four independent yet interlocking analytical layers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│  LAYER 1: Trajectory and Tool Calling Evaluation            │
│  (Tool choice accuracy, schema validation, loop detection)  │
├─────────────────────────────────────────────────────────────┤
│  LAYER 2: Environment State Testing                         │
│  (Database mutations, API idempotency, rollback checks)     │
├─────────────────────────────────────────────────────────────┤
│  LAYER 3: Calibrated Semantic Evaluation (LLM-as-a-Judge)   │
│  (CoT rubrics, RAGAS faithfulness, bias mitigation)         │
├─────────────────────────────────────────────────────────────┤
│  LAYER 4: CI/CD Pipeline Gateway and Automated Red-Teaming  │
│  (Golden datasets, pass/fail deployment thresholds, attack) │
└─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;layer-1-trajectory-and-tool-calling-evaluation&quot;&gt;Layer 1: Trajectory and Tool Calling Evaluation&lt;/h5&gt;
&lt;p&gt;An autonomous agent is not defined solely by its terminal text output; it is defined by the discrete intermediate steps it takes to navigate a task graph:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tool Selection Accuracy&lt;/strong&gt;: Did the agent select the correct tool at each decision boundary? (e.g., executing &lt;code&gt;query_database&lt;/code&gt; rather than confabulating financial metrics from internal parametric weights).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schema and Parameter Validation&lt;/strong&gt;: Did the agent structure the JSON payload with strict type conformity? Did it honor enumerated constraints and required arguments?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step-to-Goal Ratio &amp;amp; Loop Detection&lt;/strong&gt;: If an agent suddenly requires twelve conversational rounds to complete a task that typically demands three, the system has suffered a silent regression that quadruples latency and inference token expenditure. The evaluation harness must programmatically flag unproductive retry cycles before exhaustively consuming budget caps.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;layer-2-environment-state-testing-state-assertions-&quot;&gt;Layer 2: Environment State Testing (State Assertions)&lt;/h5&gt;
&lt;p&gt;The only immutable truth in computer software is the physical state mutation an application imparts to its external environment. In this layer, we recover deterministic certainty:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If an agent outputs: &lt;em&gt;“I have successfully cancelled your recurring subscription and issued a full refund,”&lt;/em&gt; the test suite does not evaluate the agent's prose. The test queries the staging database or the mock Stripe gateway directly:&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;assert user.subscription.status == &quot;cancelled&quot;
assert refund_transaction.amount == 49.99
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ephemeral Sandbox Isolation&lt;/strong&gt;: Autonomous agents must be evaluated against disposable containerized sandboxes (ephemeral Docker clusters) where outbound HTTP mutations are intercepted, logged, and validated for strict idempotency and zero unintended side-effects.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;layer-3-calibrated-semantic-evaluation-llm-as-a-judge-&quot;&gt;Layer 3: Calibrated Semantic Evaluation (LLM-as-a-Judge)&lt;/h5&gt;
&lt;p&gt;When evaluating qualitative attributes (conversational empathy, synthesis completeness, or explanatory precision), we deploy another foundation model as an evaluator. However, an uncalibrated LLM judge introduces systemic risk:&lt;/p&gt;
&lt;p&gt;Research demonstrates that LLM evaluators suffer from &lt;strong&gt;three documented cognitive biases&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Position Bias&lt;/strong&gt;: In pairwise A/B benchmarks, LLM evaluators consistently favor whichever candidate response is presented first.&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Mitigation&lt;/em&gt;: Implement automated swap-testing (running dual passes with inverted candidate ordering) and averaging score distributions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verbosity Bias&lt;/strong&gt;: Foundation models routinely award higher quality scores to longer, ornate prose, even when it harbors factual inaccuracies.&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Mitigation&lt;/em&gt;: Normalize text length constraints within the rubric and programmatically penalize unprompted redundancy.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sycophancy&lt;/strong&gt;: Evaluator models tend toward lenient, uncritical scoring to avoid conflict.&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Mitigation&lt;/em&gt;: Enforce &lt;strong&gt;Chain-of-Thought (CoT) Rubrics&lt;/strong&gt; requiring the judge to cite explicit textual evidence before emitting a discrete numerical rating (1 to 5).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For knowledge-intensive architectures, we integrate the standardized &lt;strong&gt;RAGAS&lt;/strong&gt; metric suite:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Faithfulness&lt;/strong&gt;: Is every factual claim in the response directly grounded in retrieved context? (Mathematical hallucination detection).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Answer Relevance&lt;/strong&gt;: Does the generated output directly address the user's core intent without thematic drift?&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;layer-4-ci-cd-pipeline-gateway-and-automated-red-teaming&quot;&gt;Layer 4: CI/CD Pipeline Gateway and Automated Red-Teaming&lt;/h5&gt;
&lt;p&gt;Evaluation loses all utility if conducted as a quarterly manual audit. It must be codified into the continuous delivery pipeline:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Curated Golden Datasets&lt;/strong&gt;: Version-controlled, immutable benchmark suites containing hundreds of production edge cases verified by human domain experts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Synthetic Adversarial Generation&lt;/strong&gt;: Deploying specialized attacker agents (&lt;em&gt;Adversarial Agents&lt;/em&gt;) that intentionally inject typos, ambiguity, contradictory instructions, and &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/prompt_injection/&quot;&gt;Prompt Injection&lt;/a&gt; payloads to test system resilience prior to merge.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated Deployment Gates&lt;/strong&gt;: Within GitHub Actions or GitLab CI, if a proposed prompt revision induces a &lt;strong&gt;&amp;gt;2% drop in Tool Calling precision&lt;/strong&gt; or a &lt;strong&gt;&amp;gt;0.5% degradation in factual faithfulness&lt;/strong&gt;, the Pull Request is automatically blocked.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;offline-evaluation-vs-online-telemetry&quot;&gt;Offline Evaluation vs. Online Telemetry&lt;/h4&gt;
&lt;p&gt;Testing autonomous agents does not terminate at deployment; it transitions into a closed-loop observability pipeline:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Dimension &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Offline Evaluation (Pre-Deployment) &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Online Monitoring (Production)&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;Environment&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Controlled sandboxes with synthetic mocks and test databases. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Live customer traffic and production microservice APIs.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Data Volume&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Hundreds or thousands of curated test cases (&lt;em&gt;Golden Sets&lt;/em&gt;). &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Millions of real-time multi-turn interactions.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Primary Metrics&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Schema conformance, branch coverage, prompt regression rate. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;P95/P99 latency, tool failure rate, cost per trajectory, explicit feedback (thumbs up/down).&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Core Tooling&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Pytest, RAGAS, DeepEval, Promptfoo, custom eval harnesses. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;OpenTelemetry, LangSmith, Arize Phoenix, Datadog LLM Observability.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Primary Objective&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Prevent functional and behavioral regressions before merging to &lt;em&gt;main&lt;/em&gt;. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Detect data drift, anomalous execution loops, and security abuse in real time.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h4 id=&quot;regulatory-imperative-compliance-under-the-eu-ai-act&quot;&gt;Regulatory Imperative: Compliance Under the EU AI Act&lt;/h4&gt;
&lt;p&gt;This engineering rigor has evolved from an architectural best practice into an &lt;strong&gt;uncompromising legal obligation&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Under the &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/eu_ai_act/&quot;&gt;EU AI Act&lt;/a&gt;&lt;/strong&gt;, high-risk enterprise AI systems must satisfy formal technical governance standards:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Article 15 (Accuracy, Robustness, and Cybersecurity)&lt;/strong&gt;: Formally requires that AI systems be continuously evaluated against performance degradation, unexpected environmental shifts, and adversarial exploitation throughout their lifecycle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Traceability and Audit Logging (Article 12)&lt;/strong&gt;: Mandates automated, high-fidelity logging of all intermediate tool invocations, parameters, and decision branches to permit independent external forensics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Enterprises deploying unmonitored AI agents governed solely by manual inspections face not only catastrophic operational vulnerabilities, but also severe statutory fines for lack of demonstrably verifiable technical oversight.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;5-practical-commandments-for-ai-agent-engineers&quot;&gt;5 Practical Commandments for AI Agent Engineers&lt;/h4&gt;
&lt;p&gt;If you are architecting and shipping autonomous agentic systems in production, implement these five rules immediately:&lt;/p&gt;
&lt;h5 id=&quot;1-never-touch-a-prompt-without-running-a-regression-suite&quot;&gt;1. Never Touch a Prompt Without Running a Regression Suite&lt;/h5&gt;
&lt;p&gt;Uncalibrated prompt engineering is the contemporary equivalent of patching binary code directly on a live production server. Every prompt modification must run against an automated regression harness.&lt;/p&gt;
&lt;h5 id=&quot;2-decouple-reasoning-evaluation-from-state-verification&quot;&gt;2. Decouple Reasoning Evaluation from State Verification&lt;/h5&gt;
&lt;p&gt;Never ask an agent if it completed a workflow successfully: verify the execution yourself by programmatically inspecting target database tables and microservice state machines.&lt;/p&gt;
&lt;h5 id=&quot;3-periodically-calibrate-llm-judges-with-human-reviewers&quot;&gt;3. Periodically Calibrate LLM Judges with Human Reviewers&lt;/h5&gt;
&lt;p&gt;Uncalibrated evaluator models suffer from optimistic drift. Calculate the inter-rater agreement coefficient (&lt;em&gt;Cohen’s Kappa&lt;/em&gt;) quarterly between your automated LLM judges and senior human engineers.&lt;/p&gt;
&lt;h5 id=&quot;4-benchmark-cost-and-latency-alongside-quality&quot;&gt;4. Benchmark Cost and Latency Alongside Quality&lt;/h5&gt;
&lt;p&gt;An agent boasting 98% factual precision that consumes 45 seconds and $0.35 in inference tokens per call is commercially dead on arrival. Engineering evaluation must treat &lt;strong&gt;Quality, Latency, and Cost&lt;/strong&gt; as a unified trade-off triangle.&lt;/p&gt;
&lt;h5 id=&quot;5-embed-automated-red-teaming-into-ci-cd&quot;&gt;5. Embed Automated Red-Teaming into CI/CD&lt;/h5&gt;
&lt;p&gt;The worst venue to discover an agent leaks sensitive secrets or allows prompt hijacking is when an external user posts the exploit on social media. Deploy adversarial agents designed to systematically attack your models on every commit.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;The maturity of an engineering discipline is not measured by the audacity of its prototypes, but by the &lt;strong&gt;repeatability and rigor of its validation methodologies&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Deploying autonomous AI agents without a multi-layer evaluation framework is like constructing a modern suspension bridge without testing wind resistance and structural load tolerances. Foundation reasoning models will continue their rapid march forward, but enterprise competitive advantage will not belong to whoever deploys the largest model; it will belong to &lt;strong&gt;whoever builds the most resilient, automated evaluation infrastructure to deploy intelligence with uncompromising confidence&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h5 id=&quot;sources-of-interest-&quot;&gt;Sources of Interest:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.ragas.io/&quot;&gt;&lt;strong&gt;RAGAS Framework&lt;/strong&gt;: Automated Evaluation of Retrieval Augmented Generation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/confident-ai/deepeval&quot;&gt;&lt;strong&gt;DeepEval&lt;/strong&gt;: The Open-Source LLM Evaluation Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://opentelemetry.io/docs/specs/semconv/gen-ai/&quot;&gt;&lt;strong&gt;OpenTelemetry&lt;/strong&gt;: Semantic Conventions for Generative AI and LLM Observability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/mlops_for_engineers/&quot;&gt;&lt;strong&gt;Datalaria&lt;/strong&gt;: MLOps for Engineers — From Jupyter Notebooks to Production&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/prompt_injection/&quot;&gt;&lt;strong&gt;Datalaria&lt;/strong&gt;: Prompt Injection — Cybersecurity and Vulnerabilities in AI Agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/ai_agents_part9/&quot;&gt;&lt;strong&gt;Datalaria&lt;/strong&gt;: Autopilot Series — Agentic Supervision and Orchestration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/graphrag/&quot;&gt;&lt;strong&gt;Datalaria&lt;/strong&gt;: GraphRAG — Why Vectors Aren't Enough&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/silicon_valley/&quot;&gt;&lt;strong&gt;Datalaria&lt;/strong&gt;: Silicon Valley and the PiperNet Dilemma — Runaway Optimization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/eu_ai_act/&quot;&gt;&lt;strong&gt;Datalaria&lt;/strong&gt;: EU AI Act — Practical Guide to Technical Compliance and Robustness&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29943/evaluating-and-testing-ai-agents-in-production-how-to-measure-the-unpredictable</guid>
<pubDate>Tue, 06 Oct 2026 06:11:00 +0000</pubDate>
</item>
<item>
<title>New to Bitrise CodePush? Here's What I Wish I Knew Before Setting It Up</title>
<link>https://coderlegion.com/28183/new-to-bitrise-codepush-heres-what-i-wish-i-knew-before-setting-it-up</link>
<description>&lt;h2 id=&quot;new-to-bitrise-codepush-here-s-what-i-wish-i-knew-before-setting-it-up&quot;&gt;New to Bitrise CodePush? Here's What I Wish I Knew Before Setting It Up&lt;/h2&gt;
&lt;p&gt;If you've never used CodePush before, the idea sounds almost too convenient.&lt;/p&gt;
&lt;p&gt;You find a bug in a React Native app, fix the JavaScript, and instead of waiting for another App Store or Google Play release, you can push the update directly to users.&lt;/p&gt;
&lt;p&gt;That's the part that got my attention.&lt;/p&gt;
&lt;p&gt;But once I actually started setting it up, I realized there are quite a few things that aren't obvious from that one-line explanation.&lt;/p&gt;
&lt;p&gt;So if you're new to Bitrise CodePush, here's what I learned while going through the setup.&lt;/p&gt;
&lt;h3 id=&quot;first-what-does-codepush-actually-update-&quot;&gt;First: What Does CodePush Actually Update?&lt;/h3&gt;
&lt;p&gt;The easiest way to think about CodePush is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;It updates the JavaScript and assets in your React Native app without requiring a new store release.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So if you've fixed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A JavaScript bug&lt;/li&gt;
&lt;li&gt;Some text or copy&lt;/li&gt;
&lt;li&gt;A small UI issue&lt;/li&gt;
&lt;li&gt;Another change that doesn't require native code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can potentially deliver that fix without submitting a new binary to the App Store or Google Play.&lt;/p&gt;
&lt;p&gt;But there's an important boundary.&lt;/p&gt;
&lt;p&gt;If you change native code, CodePush doesn't magically bypass the app stores.&lt;/p&gt;
&lt;p&gt;You'll still need a normal app release.&lt;/p&gt;
&lt;p&gt;That's probably the first thing I'd make sure the team understands before using it.&lt;/p&gt;
&lt;h3 id=&quot;what-bitrise-actually-gives-you&quot;&gt;What Bitrise Actually Gives You&lt;/h3&gt;
&lt;p&gt;There are two main parts to the setup.&lt;/p&gt;
&lt;h4 id=&quot;1-codepush-inside-bitrise-release-management&quot;&gt;1. CodePush inside Bitrise Release Management&lt;/h4&gt;
&lt;p&gt;This is where you manage deployments, upload releases, and control things like rollout percentages.&lt;/p&gt;
&lt;h4 id=&quot;2-the-sdk-inside-your-application&quot;&gt;2. The SDK inside your application&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;@bitrise/code-push-sdk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The SDK is responsible for checking whether an update is available and applying it.&lt;/p&gt;
&lt;p&gt;So the basic idea is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bitrise handles the release side → your app handles the update side.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Once I understood that split, the rest of the setup made a lot more sense.&lt;/p&gt;
&lt;h3 id=&quot;before-you-touch-the-code&quot;&gt;Before You Touch the Code&lt;/h3&gt;
&lt;p&gt;You'll need a Bitrise account and a Release Management app.&lt;/p&gt;
&lt;p&gt;One thing that caught my attention here is that Release Management and your normal Bitrise CI app aren't necessarily the same thing.&lt;/p&gt;
&lt;p&gt;If you're already using Bitrise CI to build your React Native app, Release Management is something you add alongside it.&lt;/p&gt;
&lt;p&gt;And if you're releasing for both iOS and Android, you'll need to account for both platforms in your setup.&lt;/p&gt;
&lt;p&gt;I'd recommend getting the basic Release Management setup working before touching the React Native project.&lt;/p&gt;
&lt;p&gt;It makes troubleshooting much easier.&lt;/p&gt;
&lt;h2 id=&quot;step-1-create-a-deployment&quot;&gt;Step 1: Create a Deployment&lt;/h2&gt;
&lt;p&gt;Inside the Release Management app, go to:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Configuration → Deployments → New deployment&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For a real project, I'd probably start with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Staging&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Production&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That separation is useful.&lt;/p&gt;
&lt;p&gt;You don't want to be testing an update and accidentally send it to everyone using the production app.&lt;/p&gt;
&lt;p&gt;You can either provide an existing deployment key or let Bitrise generate one.&lt;/p&gt;
&lt;p&gt;One thing about deployment keys&lt;/p&gt;
&lt;p&gt;This initially confused me.&lt;/p&gt;
&lt;p&gt;The deployment key looks like something you'd normally hide in a secret.&lt;/p&gt;
&lt;p&gt;But it isn't really a password.&lt;/p&gt;
&lt;p&gt;The key eventually gets included in the application, which means someone with access to the built app could potentially extract it.&lt;/p&gt;
&lt;p&gt;It's better to think of it as an identifier for the deployment rather than a secret credential.&lt;/p&gt;
&lt;p&gt;Your API token is the thing you actually need to protect.&lt;/p&gt;
&lt;p&gt;Step 2: Install the SDK&lt;/p&gt;
&lt;p&gt;The first command is simple:&lt;/p&gt;
&lt;p&gt;npm install @bitrise/code-push-sdk&lt;/p&gt;
&lt;p&gt;After that, things depend on how your React Native app was created.&lt;/p&gt;
&lt;p&gt;And this is one place where I wouldn't recommend blindly copying configuration from a random tutorial.&lt;/p&gt;
&lt;p&gt;Expo and bare React Native aren't configured in exactly the same way.&lt;/p&gt;
&lt;p&gt;If You're Using Expo&lt;/p&gt;
&lt;p&gt;With Expo, you add the CodePush plugin to your app.json.&lt;/p&gt;
&lt;p&gt;You'll need your deployment key and the CodePush server URL.&lt;/p&gt;
&lt;p&gt;The URL follows this pattern:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://&amp;lt;workspace-slug&amp;gt;.codepush.bitrise.io&quot;&gt;https://&amp;lt;workspace-slug&amp;gt;.codepush.bitrise.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then run:&lt;/p&gt;
&lt;p&gt;npx expo prebuild&lt;/p&gt;
&lt;p&gt;I like doing this step because it lets you check whether the configuration actually made its way into the generated native project.&lt;/p&gt;
&lt;p&gt;If You're Using Bare React Native&lt;/p&gt;
&lt;p&gt;This takes a little more work.&lt;/p&gt;
&lt;p&gt;On iOS, you'll need to make a change in AppDelegate.swift so CodePush can resolve the active release bundle instead of always using the default bundled JavaScript.&lt;/p&gt;
&lt;p&gt;You'll also configure the deployment key and server URL in Info.plist.&lt;/p&gt;
&lt;p&gt;On Android, the configuration goes into strings.xml, along with some Gradle configuration and a change to how MainApplication.kt resolves the JavaScript bundle.&lt;/p&gt;
&lt;p&gt;None of these changes are particularly complicated.&lt;/p&gt;
&lt;p&gt;They're just easy places to make a small mistake.&lt;/p&gt;
&lt;p&gt;That's why I found it better to follow the Bitrise documentation closely rather than trying to recreate the setup from memory.&lt;/p&gt;
&lt;p&gt;Step 3: Think About the User Experience&lt;/p&gt;
&lt;p&gt;This was probably the part I didn't think about enough at first.&lt;/p&gt;
&lt;p&gt;It's easy to look at CodePush as:&lt;/p&gt;
&lt;p&gt;&quot;There's an update, download it.&quot;&lt;/p&gt;
&lt;p&gt;But you actually have choices about how that update reaches the user.&lt;/p&gt;
&lt;p&gt;For example, with:&lt;/p&gt;
&lt;p&gt;codePush.sync()&lt;/p&gt;
&lt;p&gt;you can use different behaviours.&lt;/p&gt;
&lt;p&gt;Silent update&lt;/p&gt;
&lt;p&gt;The update downloads in the background and is applied when the app restarts.&lt;/p&gt;
&lt;p&gt;For the user, the whole thing can be almost invisible.&lt;/p&gt;
&lt;p&gt;Update when the app resumes&lt;/p&gt;
&lt;p&gt;Instead of checking only when the app starts, the app can check when it comes back into the foreground.&lt;/p&gt;
&lt;p&gt;Interactive update&lt;/p&gt;
&lt;p&gt;The user gets feedback that an update is available and can be shown the download process.&lt;/p&gt;
&lt;p&gt;For mandatory updates, you can also prevent the user from simply dismissing them.&lt;/p&gt;
&lt;p&gt;You can even use progress callbacks if you want to show download progress.&lt;/p&gt;
&lt;p&gt;I think this is worth deciding before you ship your first update.&lt;/p&gt;
&lt;p&gt;The technically easiest option isn't necessarily the best experience for your users.&lt;/p&gt;
&lt;p&gt;Step 4: Build and Release&lt;/p&gt;
&lt;p&gt;Once the application is configured, you need to create the JavaScript bundle.&lt;/p&gt;
&lt;p&gt;For a bare React Native project:&lt;/p&gt;
&lt;p&gt;npx react-native bundle \&lt;br&gt;
  --platform ios \&lt;br&gt;
  --dev false \&lt;br&gt;
  --entry-file index.js \&lt;br&gt;
  --bundle-output ./build/main.jsbundle \&lt;br&gt;
  --assets-dest ./build&lt;/p&gt;
&lt;p&gt;For Expo:&lt;/p&gt;
&lt;p&gt;expo export:embed&lt;/p&gt;
&lt;p&gt;You then package the generated files and upload them to Release Management.&lt;/p&gt;
&lt;p&gt;You can do this manually from the dashboard, which is probably the easiest way to test your first release.&lt;/p&gt;
&lt;p&gt;Once you've confirmed everything works, that's when I'd start thinking about automation.&lt;/p&gt;
&lt;p&gt;Don't Immediately Send It to Everyone&lt;/p&gt;
&lt;p&gt;This is one feature I'd definitely use in production.&lt;/p&gt;
&lt;p&gt;When you create the release, you can control the rollout.&lt;/p&gt;
&lt;p&gt;You can target specific app versions and use version ranges such as:&lt;/p&gt;
&lt;p&gt;1.2.x&lt;/p&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;p&gt;&amp;gt;=1.2.3 &amp;lt;1.2.7&lt;/p&gt;
&lt;p&gt;You can also mark an update as mandatory and control the percentage of users who receive it.&lt;/p&gt;
&lt;p&gt;That last part is important.&lt;/p&gt;
&lt;p&gt;Imagine you've made a JavaScript change that you think is completely safe.&lt;/p&gt;
&lt;p&gt;Instead of immediately sending it to 100% of your users, you can start with a smaller percentage.&lt;/p&gt;
&lt;p&gt;If everything looks good, increase the rollout.&lt;/p&gt;
&lt;p&gt;That gives you a little more room to catch problems before they become everyone's problem.&lt;/p&gt;
&lt;p&gt;What About Automation?&lt;/p&gt;
&lt;p&gt;Once you've manually released a couple of updates, you'll probably notice the repetitive part:&lt;/p&gt;
&lt;p&gt;Build the bundle.&lt;br&gt;
Zip it.&lt;br&gt;
Upload it.&lt;br&gt;
Do it again for the other platform.&lt;/p&gt;
&lt;p&gt;It works.&lt;/p&gt;
&lt;p&gt;But I wouldn't want to do that every week.&lt;/p&gt;
&lt;p&gt;That's where Bitrise CI comes in.&lt;/p&gt;
&lt;p&gt;You can automate the build and upload process so that a workflow handles it for you.&lt;/p&gt;
&lt;p&gt;One approach I like is connecting the release to an approved and labelled pull request rather than automatically releasing every merge.&lt;/p&gt;
&lt;p&gt;That gives you a deliberate point where someone says:&lt;/p&gt;
&lt;p&gt;&quot;This is ready to ship.&quot;&lt;/p&gt;
&lt;p&gt;The CI system then takes care of the repetitive work.&lt;/p&gt;
&lt;p&gt;I wrote separately about that automation process because there are a few more details around credentials, versioning, and workflow configuration.&lt;/p&gt;
&lt;p&gt;My Checklist Before the First Release&lt;/p&gt;
&lt;p&gt;If I were setting this up on another project, this is the checklist I'd keep nearby:&lt;/p&gt;
&lt;p&gt;Bitrise Release Management is configured&lt;/p&gt;
&lt;p&gt;iOS and Android are accounted for&lt;/p&gt;
&lt;p&gt;Staging and Production deployments are created&lt;/p&gt;
&lt;p&gt;@bitrise/code-push-sdk is installed&lt;/p&gt;
&lt;p&gt;Expo or native React Native configuration is complete&lt;/p&gt;
&lt;p&gt;The app can successfully check for updates&lt;/p&gt;
&lt;p&gt;The sync behaviour has been chosen&lt;/p&gt;
&lt;p&gt;The first release is tested with a small rollout&lt;/p&gt;
&lt;p&gt;Production releases have an automation plan&lt;/p&gt;
&lt;p&gt;So, Is CodePush Worth It?&lt;/p&gt;
&lt;p&gt;I think it makes the most sense when you regularly have small JavaScript-level fixes that don't justify going through another full app-store release.&lt;/p&gt;
&lt;p&gt;It's not a replacement for your normal mobile release pipeline.&lt;/p&gt;
&lt;p&gt;Native changes still need a normal build and store submission.&lt;/p&gt;
&lt;p&gt;But if your team has ever had to submit a new mobile build because of something that could have been fixed entirely in JavaScript, that's the gap CodePush is trying to close.&lt;/p&gt;
&lt;p&gt;For me, the biggest takeaway wasn't just:&lt;/p&gt;
&lt;p&gt;&quot;I can push an update without the App Store.&quot;&lt;/p&gt;
&lt;p&gt;It was realizing that there are several decisions around how that update gets delivered, who receives it, and what the user experiences when it arrives.&lt;/p&gt;
&lt;p&gt;That's the part I'd pay attention to before putting CodePush into a production app.&lt;/p&gt;
&lt;p&gt;Have You Used CodePush?&lt;/p&gt;
&lt;p&gt;Have you used CodePush or another OTA update solution with React Native?&lt;/p&gt;
&lt;p&gt;I'd love to hear how you handled it, especially if you ran into problems during setup.&lt;/p&gt;
&lt;p&gt;What would you add to this checklist?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28183/new-to-bitrise-codepush-heres-what-i-wish-i-knew-before-setting-it-up</guid>
<pubDate>Tue, 22 Sep 2026 15:32:27 +0000</pubDate>
</item>
<item>
<title>Let's not water down the terms Continuous Integration and Continuous Delivery</title>
<link>https://coderlegion.com/27936/lets-not-water-down-the-terms-continuous-integration-and-continuous-delivery</link>
<description>&lt;p&gt;One of the most perplexing discussions I've had in recent times was about CI/CD. I've earned my CI/CD scout badge at this stage in my career, so the confusion caught me off guard. This post briefly sets out what Continuous Integration (CI) and Continuous Delivery (CD) mean and addresses the most common misconceptions in the discussion.&lt;/p&gt;
&lt;p&gt;My point isn't just semantic. These terms carry decades of hard-won engineering practice, and when we let their meaning drift or decay, we lose crucial wisdom.&lt;/p&gt;
&lt;h3 id=&quot;continuous-integration&quot;&gt;Continuous Integration&lt;/h3&gt;
&lt;p&gt;Continuous Integration is well named compared to many concepts in our industry. The term &quot;integration&quot; refers to merging your changes into the main branch in version control, and the term &quot;continuous&quot; means you do it often. When Kent Beck popularized the term in &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.oreilly.com/library/view/extreme-programming-explained/0201616416/&quot;&gt;Extreme Programming Explained&lt;/a&gt; (1999), he set an upper limit of a day. After 26 years of progress, you should expect to be doing it far more frequently than that.&lt;/p&gt;
&lt;p&gt;You might understand the concept better if we look at examples that don't fit the definition. If you have code on your machine that's more than a day old, you're not practicing Continuous Integration. If you have a branch that's more than a day old, you're not practicing Continuous Integration. It doesn't matter how often you merge from main into your branch, because main is completely unaware of changes, like yours, that haven't been merged yet. That means you have diverging software versions, and at some point, you have to make them all converge.&lt;/p&gt;
&lt;h3 id=&quot;continuous-delivery&quot;&gt;Continuous Delivery&lt;/h3&gt;
&lt;p&gt;Continuous Delivery is an approach focused on delivering all changes to users quickly, safely, and sustainably. You keep your code in a deployable state at all times and reject the idea of separate integration, testing, and hardening phases. There are specific technical practices involved here because you need a deployment pipeline that reduces risk and increases your confidence that the software version works.&lt;/p&gt;
&lt;p&gt;At this stage in our industry's history, almost everything involved in answering the question &quot;Is this deployable?&quot; should be automated as part of the deployment pipeline. A decade ago, perhaps, you could reasonably list a few stages that were economically unviable for automation. The scale and pace of modern software delivery mean this is rarely the case now.&lt;/p&gt;
&lt;p&gt;The simple test is to ask, &quot;Is the software deployable?&quot; If you can't answer this question every day, you're not doing Continuous Delivery. In their book on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.oreilly.com/library/view/continuous-delivery-reliable/9780321670250/&quot;&gt;Continuous Delivery&lt;/a&gt; (2010), Dave Farley and Jez Humble detail the principles and practices that let you answer this question, and the research has increased our confidence that this is what good software delivery looks like.&lt;/p&gt;
&lt;h3 id=&quot;misconceptions&quot;&gt;Misconceptions&lt;/h3&gt;
&lt;p&gt;Let's turn our attention to where the conversation left the trail.&lt;/p&gt;
&lt;h4 id=&quot;ci-doesn-t-just-mean-automated-builds&quot;&gt;CI doesn't just mean automated builds&lt;/h4&gt;
&lt;p&gt;One of the most common sources of confusion is that CI just means automated builds and tests. While it's difficult to practice Continuous Integration without automation, the crucial property of Continuous Integration is that all changes are in a shared mainline, not on developer machines or in long-lived branches. Having more than 3 branches or keeping branches open for more than a day slows delivery throughput and makes the software less stable, as reinforced by &lt;a rel=&quot;nofollow&quot; href=&quot;https://dora.dev/research/&quot;&gt;long-running research&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There's a human process at the heart of CI: each developer chooses to commit their changes frequently to main. Without people deciding to do this, there is no CI.&lt;/p&gt;
&lt;h4 id=&quot;ci-doesn-t-allow-feature-branching&quot;&gt;CI doesn't allow feature branching&lt;/h4&gt;
&lt;p&gt;There's a misconception that you can do CI and choose a branching strategy, like feature branches. Any branching strategy that keeps a change out of main for more than a day cannot be called CI. You may still choose to use feature branches despite the drawbacks, but you have to stop calling it Continuous Integration, as you aren't continuously integrating all changes.&lt;/p&gt;
&lt;h4 id=&quot;cd-doesn-t-mean-continuous-deployment-though-it-can&quot;&gt;CD doesn't mean Continuous Deployment, though it can&lt;/h4&gt;
&lt;p&gt;CD means &quot;Continuous Delivery&quot;. It means your software is deployable at all times, and you can test this daily by asking, &quot;Can we deploy the software?&quot; If you don't have a way to answer the question confidently, you're not doing Continuous Delivery. You may choose to deploy every validated software version automatically, which is Continuous Deployment, or you may need to be more tactical about when you deploy. Either is fine.&lt;/p&gt;
&lt;p&gt;For both Continuous Delivery and Continuous Deployment, you need high levels of automation and sufficient risk coverage within that automation so you know you can deploy. Continuous Delivery should mean you can deploy at the press of a button, and Continuous Deployment removes the requirement to press the button.&lt;/p&gt;
&lt;h4 id=&quot;you-can-t-do-cd-without-ci&quot;&gt;You can't do CD without CI&lt;/h4&gt;
&lt;p&gt;Some folks were confident that you can do Continuous Delivery without doing Continuous Integration, but the literature on this is very clear. Farley and Humble explicitly include Continuous Integration as a required practice. There's a whole chapter dedicated to it (p. 55 of Continuous Delivery) that adequately explains how it should work and why it's not optional.&lt;/p&gt;
&lt;h4 id=&quot;ci-cd-isn-t-vague-or-open-to-interpretation&quot;&gt;CI/CD isn't vague or open to interpretation&lt;/h4&gt;
&lt;p&gt;Another claim that came up more than once was that CI/CD is vague, open to interpretation, or lacks the detail on &quot;how&quot; you should apply the approach. This does a disservice to those who conscientiously captured the approach, whether that's Kent Beck describing Continuous Integration in &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.oreilly.com/library/view/extreme-programming-explained/0201616416/&quot;&gt;&lt;em&gt;Extreme Programming Explained&lt;/em&gt;&lt;/a&gt; (1999) or Farley and Humble detailing both Continuous Integration and Continuous Delivery in their book &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.oreilly.com/library/view/continuous-delivery-reliable/9780321670250/&quot;&gt;&lt;em&gt;Continuous Delivery&lt;/em&gt;&lt;/a&gt; (2010).&lt;/p&gt;
&lt;h3 id=&quot;resist-the-diffusion&quot;&gt;Resist the diffusion&lt;/h3&gt;
&lt;p&gt;There is a constant temptation in the tech industry to allow &lt;a rel=&quot;nofollow&quot; href=&quot;https://martinfowler.com/bliki/SemanticDiffusion.html&quot;&gt;semantic diffusion&lt;/a&gt; to erode our progress. When we package up a cartload of wisdom into a term, we have to resist the diffusion that follows when people oversimplify our term, remove crucial parts of the whole, or try to brand something with the label that pushes us backward.&lt;/p&gt;
&lt;p&gt;We have a history of giving up on these battles, as we seem to have done with Agile and DevOps. These terms had an important meaning that represented a significant step forward for our industry, and losing our energy in their defense lets the perpetual &quot;Late Majority&quot; and &quot;Laggards&quot; (from &lt;a rel=&quot;nofollow&quot; href=&quot;https://geoffreyamoore.com/book/crossing-the-chasm/&quot;&gt;Crossing the Chasm&lt;/a&gt;) drag us all back into their old ways.&lt;/p&gt;
&lt;p&gt;CI and CD deserve better.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27936/lets-not-water-down-the-terms-continuous-integration-and-continuous-delivery</guid>
<pubDate>Mon, 21 Sep 2026 11:16:28 +0000</pubDate>
</item>
<item>
<title>Release Gates Should Be Risk-Based, Not Checklist-Based</title>
<link>https://coderlegion.com/26542/release-gates-should-be-risk-based-not-checklist-based</link>
<description>&lt;p&gt;A small copy change and a database migration probably shouldn't go through exactly the same release process.&lt;/p&gt;
&lt;p&gt;Yet in many teams, they do.&lt;/p&gt;
&lt;p&gt;Both need the same approvals. Both follow the same checklist. Both wait for the same people, regardless of what actually changed.&lt;/p&gt;
&lt;p&gt;The intention is good: create a consistent process and prevent risky releases.&lt;/p&gt;
&lt;p&gt;But consistency doesn't have to mean treating every change as if it carries the same risk.&lt;/p&gt;
&lt;p&gt;I think a better question is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What evidence and controls does this particular release need?&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-problem-with-a-fixed-checklist&quot;&gt;The problem with a fixed checklist&lt;/h3&gt;
&lt;p&gt;A typical release checklist might include things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CI passed&lt;/li&gt;
&lt;li&gt;Regression tests passed&lt;/li&gt;
&lt;li&gt;Security scan completed&lt;/li&gt;
&lt;li&gt;QA approved&lt;/li&gt;
&lt;li&gt;Product owner approved&lt;/li&gt;
&lt;li&gt;Rollback plan available&lt;/li&gt;
&lt;li&gt;Monitoring checked&lt;/li&gt;
&lt;li&gt;Deployment window confirmed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of those are bad checks. Most are useful, and some may be mandatory for every release.&lt;/p&gt;
&lt;p&gt;The problem starts when the checklist becomes the decision.&lt;/p&gt;
&lt;p&gt;If every box is ticked, the release is considered ready. If one box isn't ticked, the release stops.&lt;/p&gt;
&lt;p&gt;That can create two very different problems.&lt;/p&gt;
&lt;p&gt;A low-risk change may wait for approvals that don't add meaningful value. Meanwhile, a high-risk change may pass the same checklist without receiving the additional scrutiny it actually needs.&lt;/p&gt;
&lt;p&gt;The process is consistent, but the level of control isn't necessarily appropriate.&lt;/p&gt;
&lt;h3 id=&quot;two-releases-two-very-different-risks&quot;&gt;Two releases, two very different risks&lt;/h3&gt;
&lt;p&gt;Imagine two release candidates.&lt;/p&gt;
&lt;h4 id=&quot;release-a&quot;&gt;Release A&lt;/h4&gt;
&lt;p&gt;A small copy change on a low-impact page.&lt;/p&gt;
&lt;p&gt;The change is isolated. There are no dependency updates, database changes, or changes to authentication. Automated checks pass, and rollback is straightforward.&lt;/p&gt;
&lt;h4 id=&quot;release-b&quot;&gt;Release B&lt;/h4&gt;
&lt;p&gt;An authentication refactor that also includes a database migration and a new external dependency.&lt;/p&gt;
&lt;p&gt;The automated checks pass, but the rollback procedure hasn't been exercised recently. The migration affects production data, and the change touches a critical user workflow.&lt;/p&gt;
&lt;p&gt;Should both releases require exactly the same evidence?&lt;/p&gt;
&lt;p&gt;Probably not.&lt;/p&gt;
&lt;p&gt;Release A may be suitable for an automated decision once the required baseline checks are satisfied.&lt;/p&gt;
&lt;p&gt;Release B deserves more attention to migration validation, security, dependency behavior, rollback readiness, and possibly an explicit approval from the relevant owner.&lt;/p&gt;
&lt;p&gt;The point isn't that Release B must automatically be rejected.&lt;/p&gt;
&lt;p&gt;The point is that &lt;strong&gt;the risk should determine which additional questions need to be answered.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;start-with-a-baseline-not-with-zero-gates&quot;&gt;Start with a baseline, not with zero gates&lt;/h3&gt;
&lt;p&gt;Risk-based doesn't mean optional quality.&lt;/p&gt;
&lt;p&gt;Every team still needs a baseline of controls that applies to all releases.&lt;/p&gt;
&lt;p&gt;For example, a team might require a versioned artifact, successful required CI checks, no unresolved blocking security findings, and a valid deployment path.&lt;/p&gt;
&lt;p&gt;Those are the minimum conditions for entering the release decision process.&lt;/p&gt;
&lt;p&gt;Then, additional controls can be activated based on the change.&lt;/p&gt;
&lt;p&gt;Conceptually:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Required baseline
       +
Change-specific risk
       +
Relevant evidence
       =
Release decision
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The baseline prevents teams from bypassing essential controls just because a change looks small.&lt;/p&gt;
&lt;p&gt;The risk-specific layer prevents every release from being forced through the same expensive process.&lt;/p&gt;
&lt;p&gt;Both matter.&lt;/p&gt;
&lt;h3 id=&quot;what-should-trigger-additional-gates-&quot;&gt;What should trigger additional gates?&lt;/h3&gt;
&lt;p&gt;I would start with a small number of risk dimensions rather than trying to invent a complicated scoring model immediately.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Change impact.&lt;/strong&gt; How many services, users, or critical workflows could be affected? Is the change isolated, or does it cross system boundaries?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Change type.&lt;/strong&gt; Does it involve authentication, payments, permissions, infrastructure, database migrations, or a major dependency update?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Evidence quality.&lt;/strong&gt; Do we have relevant test coverage? Was the migration validated? Are the results current and tied to the actual release candidate?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Operational readiness.&lt;/strong&gt; Is rollback feasible? Are health checks and monitoring available? Is the team prepared to respond if something goes wrong?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Business context.&lt;/strong&gt; Is this a routine release, a critical hotfix, or a change during a high-traffic period? What is the cost of delaying it compared with the risk of shipping it?&lt;/p&gt;
&lt;p&gt;Not every team needs all of these dimensions. The important thing is to identify the ones that actually influence release risk in your environment.&lt;/p&gt;
&lt;h3 id=&quot;a-simple-risk-based-gate-model&quot;&gt;A simple risk-based gate model&lt;/h3&gt;
&lt;p&gt;A lightweight implementation could look something like this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Release context &lt;/th&gt;&lt;th&gt;Additional evidence or control&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Small, isolated change &lt;/td&gt;&lt;td&gt;Baseline checks may be sufficient&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Database migration &lt;/td&gt;&lt;td&gt;Migration validation, backup and rollback assessment&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Authentication or permission change &lt;/td&gt;&lt;td&gt;Relevant security review and targeted regression&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Critical dependency update &lt;/td&gt;&lt;td&gt;Compatibility and dependency-impact evidence&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;High-impact service change &lt;/td&gt;&lt;td&gt;Operational readiness and appropriate owner approval&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Reduced test coverage &lt;/td&gt;&lt;td&gt;Explicit risk assessment and compensating controls&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Unverified rollback &lt;/td&gt;&lt;td&gt;Release held or exceptional approval under defined policy&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This isn't a universal policy. It's an example of how a team could make its controls more relevant to the change.&lt;/p&gt;
&lt;p&gt;The important part is that the gates are triggered by meaningful risk, not by the desire to add more process.&lt;/p&gt;
&lt;h3 id=&quot;some-gates-should-never-be-optional&quot;&gt;Some gates should never be optional&lt;/h3&gt;
&lt;p&gt;There is an important distinction between a conditional gate and a mandatory control.&lt;/p&gt;
&lt;p&gt;A team may decide that certain conditions are non-negotiable.&lt;/p&gt;
&lt;p&gt;For example, a critical security vulnerability in a release artifact might be an automatic blocker. A missing required approval for a regulated change might also be a blocker. An artifact that cannot be identified or verified may not be eligible for release at all.&lt;/p&gt;
&lt;p&gt;Those rules should not disappear because someone labels the change &quot;low risk.&quot;&lt;/p&gt;
&lt;p&gt;Risk-based release governance should make mandatory controls explicit.&lt;/p&gt;
&lt;p&gt;It should also make exceptions explicit.&lt;/p&gt;
&lt;p&gt;If an exception is allowed, the process should define who can authorize it, what evidence is required, how long it remains valid, and what compensating controls are needed.&lt;/p&gt;
&lt;p&gt;Otherwise, &quot;risk-based&quot; can quietly become &quot;we skip the checks when we're in a hurry.&quot;&lt;/p&gt;
&lt;h3 id=&quot;conditional-go-is-where-this-becomes-interesting&quot;&gt;Conditional GO is where this becomes interesting&lt;/h3&gt;
&lt;p&gt;Not every release fits neatly into GO or NO GO.&lt;/p&gt;
&lt;p&gt;Sometimes the evidence is sufficient to proceed, but only under specific conditions.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Decision: CONDITIONAL GO

Reason:
- Required automated checks passed
- No blocking security findings
- Minor observability gap remains

Conditions:
- Enhanced monitoring during rollout
- Named rollback owner available
- Rollout limited to a small user segment
- Production health reviewed before expansion
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's very different from simply ignoring a warning.&lt;/p&gt;
&lt;p&gt;The risk is acknowledged. The conditions are explicit. Someone is responsible for ensuring they are met.&lt;/p&gt;
&lt;p&gt;And if the conditions aren't satisfied, the release should not proceed under that authorization.&lt;/p&gt;
&lt;p&gt;A conditional decision should never be a vague way of saying, &quot;We know there's a problem, but let's ship anyway.&quot;&lt;/p&gt;
&lt;h3 id=&quot;human-approval-should-add-judgment&quot;&gt;Human approval should add judgment&lt;/h3&gt;
&lt;p&gt;I'm not against human approval gates.&lt;/p&gt;
&lt;p&gt;But I think we need to be careful about what we're asking humans to do.&lt;/p&gt;
&lt;p&gt;If every release requires the same person to click Approve, regardless of risk, the approval can become routine.&lt;/p&gt;
&lt;p&gt;People get used to seeing green checks. They trust the process. Eventually, the approval becomes another step to get through.&lt;/p&gt;
&lt;p&gt;A more useful model is to involve humans when their judgment actually adds value.&lt;/p&gt;
&lt;p&gt;For example, when the evidence is incomplete, the business impact is significant, the release includes an unusual change, or the risk exceeds the team's normal policy.&lt;/p&gt;
&lt;p&gt;The approver should be able to see the relevant context without manually reconstructing it from five different systems.&lt;/p&gt;
&lt;p&gt;That means automation should collect and structure the evidence, while humans resolve the questions that cannot be answered by a simple rule.&lt;/p&gt;
&lt;h3 id=&quot;don-t-confuse-a-risk-score-with-a-decision&quot;&gt;Don't confuse a risk score with a decision&lt;/h3&gt;
&lt;p&gt;It's tempting to turn all of this into a single number.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Release Risk Score: 72
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But what does 72 actually mean?&lt;/p&gt;
&lt;p&gt;Is it 72 out of 100? Is higher better or worse? Which signals contributed to it? Is a missing rollback plan equivalent to a low test coverage percentage? Can one critical finding be averaged away by ten healthy signals?&lt;/p&gt;
&lt;p&gt;A score can be useful as a summary, but it shouldn't hide the underlying evidence.&lt;/p&gt;
&lt;p&gt;For example, a release with excellent test coverage and one critical security blocker shouldn't become acceptable just because the average score looks good.&lt;/p&gt;
&lt;p&gt;I would rather have a smaller, explainable model than a sophisticated number that nobody can defend.&lt;/p&gt;
&lt;p&gt;The decision should be understandable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Decision: NO GO

Blocking reason:
- Required migration rollback evidence is missing

Supporting context:
- Automated tests passed
- Security checks passed
- Production impact is high
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The score, if one exists, should support the explanation — not replace it.&lt;/p&gt;
&lt;h3 id=&quot;evidence-needs-to-be-current&quot;&gt;Evidence needs to be current&lt;/h3&gt;
&lt;p&gt;Another thing that matters is whether the evidence actually belongs to the release being evaluated.&lt;/p&gt;
&lt;p&gt;A test report from yesterday may not be relevant if the artifact changed this morning.&lt;/p&gt;
&lt;p&gt;A security scan may be outdated after a dependency update.&lt;/p&gt;
&lt;p&gt;A staging validation may no longer represent production if the configuration changed.&lt;/p&gt;
&lt;p&gt;This is easy to overlook when evidence is scattered across different tools.&lt;/p&gt;
&lt;p&gt;A release decision should know which artifact, commit, configuration, or release candidate the evidence refers to.&lt;/p&gt;
&lt;p&gt;Otherwise, you can end up with a technically complete checklist built from evidence that no longer describes the thing you're about to ship.&lt;/p&gt;
&lt;h3 id=&quot;start-with-the-incidents-you-ve-already-had&quot;&gt;Start with the incidents you've already had&lt;/h3&gt;
&lt;p&gt;If I were introducing risk-based gates to a small team, I wouldn't begin by designing a huge governance framework.&lt;/p&gt;
&lt;p&gt;I'd start with the release problems the team has actually experienced.&lt;/p&gt;
&lt;p&gt;Which changes caused incidents?&lt;/p&gt;
&lt;p&gt;What evidence was missing?&lt;/p&gt;
&lt;p&gt;Which risks were known but not visible to the approver?&lt;/p&gt;
&lt;p&gt;What would have helped the team make a better decision?&lt;/p&gt;
&lt;p&gt;Maybe the team has had repeated problems with database migrations. Then migration validation and rollback readiness deserve more attention.&lt;/p&gt;
&lt;p&gt;Maybe authentication changes have caused regressions. Then targeted security and QA evidence may be appropriate.&lt;/p&gt;
&lt;p&gt;Maybe the biggest issue is deployment configuration drift. Then artifact and environment consistency might be the most valuable controls.&lt;/p&gt;
&lt;p&gt;The best gates are often the ones that address real failure modes, not the ones that make a process diagram look comprehensive.&lt;/p&gt;
&lt;h3 id=&quot;the-goal-is-fewer-surprises-not-more-approvals&quot;&gt;The goal is fewer surprises, not more approvals&lt;/h3&gt;
&lt;p&gt;A good release process shouldn't make every release feel like a compliance exercise.&lt;/p&gt;
&lt;p&gt;It should help teams move quickly when the risk is low and slow down when there is a good reason to do so.&lt;/p&gt;
&lt;p&gt;That means some releases may need very little additional intervention.&lt;/p&gt;
&lt;p&gt;Others may need more evidence, a mitigation plan, or an explicit decision from someone with the right authority.&lt;/p&gt;
&lt;p&gt;The process should be predictable, but not blind to context.&lt;/p&gt;
&lt;p&gt;And after the release, the team should be able to explain why it was allowed to proceed.&lt;/p&gt;
&lt;p&gt;Not just:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;All the boxes were green.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;The required baseline was satisfied, the change-specific risks were evaluated, and the remaining risk was accepted under the appropriate policy.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That's a much stronger answer.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I don't think the future of release governance is a checklist with fifty mandatory steps.&lt;/p&gt;
&lt;p&gt;I think it's a smaller set of meaningful controls, activated by the risk of the change, supported by evidence, and connected to clear decision authority.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The goal isn't to make releases harder. It's to make the right releases easy, and the risky ones harder to ship by accident.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This is the fiftth article in my &lt;strong&gt;Beyond Green CI&lt;/strong&gt; series.&lt;/p&gt;
&lt;p&gt;How does your team handle release gates today? Are they mostly fixed, or do you adjust the evidence and approvals based on the risk of the change?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26542/release-gates-should-be-risk-based-not-checklist-based</guid>
<pubDate>Wed, 09 Sep 2026 05:04:03 +0000</pubDate>
</item>
<item>
<title>A Failed Test Is a Clue, Not a Diagnosis</title>
<link>https://coderlegion.com/26540/a-failed-test-is-a-clue-not-a-diagnosis</link>
<description>&lt;p&gt;An end-to-end test fails.&lt;/p&gt;
&lt;p&gt;The report is red.&lt;/p&gt;
&lt;p&gt;Someone asks, “Is it a bug?”&lt;/p&gt;
&lt;p&gt;Maybe.&lt;/p&gt;
&lt;p&gt;But at that moment, all we really know is that the test didn't observe the expected behavior.&lt;/p&gt;
&lt;p&gt;That distinction sounds small, but it changed the way I investigate failures.&lt;/p&gt;
&lt;p&gt;When I started working more deeply with automation, I gradually learned that a failed test is not a diagnosis. It's the beginning of an investigation.&lt;/p&gt;
&lt;h3 id=&quot;the-same-failure-can-have-very-different-causes&quot;&gt;The same failure can have very different causes&lt;/h3&gt;
&lt;p&gt;Imagine an automated checkout test that expects an order to be created.&lt;/p&gt;
&lt;p&gt;The test fails because the order confirmation never appears.&lt;/p&gt;
&lt;p&gt;The cause could be an application defect. But it could also be a failed API request, unavailable service, incorrect test data, expired credentials, a deployment issue, an environment configuration problem, or a timing issue in the test.&lt;/p&gt;
&lt;p&gt;All of those can produce a similar result in the report.&lt;/p&gt;
&lt;p&gt;If we immediately label the failure as a product bug, we may send developers looking in the wrong place.&lt;/p&gt;
&lt;p&gt;If we immediately label it as a flaky test, we may hide a real defect.&lt;/p&gt;
&lt;p&gt;The first job is to find out which one it is.&lt;/p&gt;
&lt;h3 id=&quot;start-with-the-failure-not-the-assumption&quot;&gt;Start with the failure, not the assumption&lt;/h3&gt;
&lt;p&gt;My first step is to understand exactly what failed.&lt;/p&gt;
&lt;p&gt;Was it an assertion? A timeout? A missing element? An HTTP error? Did the test fail before the application was even reached?&lt;/p&gt;
&lt;p&gt;The details matter.&lt;/p&gt;
&lt;p&gt;A test that receives a 500 response is different from a test that cannot connect to the server. A timeout waiting for a button is different from a timeout waiting for an API response.&lt;/p&gt;
&lt;p&gt;I also want to know whether the failure is reproducible.&lt;/p&gt;
&lt;p&gt;Does it happen every time? Only in CI? Only in one environment? Only when tests run in parallel? Did it start after a particular deployment?&lt;/p&gt;
&lt;p&gt;Those questions help narrow the problem before anyone starts changing code.&lt;/p&gt;
&lt;h3 id=&quot;check-whether-the-application-is-actually-healthy&quot;&gt;Check whether the application is actually healthy&lt;/h3&gt;
&lt;p&gt;One of the most useful habits I developed was to stop looking only at the test report.&lt;/p&gt;
&lt;p&gt;If an API test fails, I want to know whether the API is responding outside the test.&lt;/p&gt;
&lt;p&gt;If an end-to-end test fails, I want to know whether the underlying service is healthy.&lt;/p&gt;
&lt;p&gt;A simple request, a health endpoint, or a look at the service logs can reveal that the test is only reporting a symptom of a larger problem.&lt;/p&gt;
&lt;p&gt;For example, a login test might fail because the authentication service is unavailable.&lt;/p&gt;
&lt;p&gt;Changing the Playwright locator won't fix that.&lt;/p&gt;
&lt;p&gt;Neither will adding another ten seconds to the timeout.&lt;/p&gt;
&lt;h3 id=&quot;environment-differences-matter&quot;&gt;Environment differences matter&lt;/h3&gt;
&lt;p&gt;I remember how confusing DEV, QA, STG, and PROD were when I first entered QA.&lt;/p&gt;
&lt;p&gt;Today, environment differences are one of the first things I consider during an investigation.&lt;/p&gt;
&lt;p&gt;The same code can behave differently because of configuration, data, dependencies, feature flags, external integrations, or infrastructure.&lt;/p&gt;
&lt;p&gt;If a test passes locally but fails in CI, I want to compare the conditions.&lt;/p&gt;
&lt;p&gt;Are the same versions running? Is the test using the same configuration? Are required services available? Is the data equivalent? Are there resource constraints? Is there a difference in browser or runtime versions?&lt;/p&gt;
&lt;p&gt;“Works on my machine” isn't a diagnosis either.&lt;/p&gt;
&lt;p&gt;It's another clue.&lt;/p&gt;
&lt;h3 id=&quot;logs-connect-the-test-to-the-system&quot;&gt;Logs connect the test to the system&lt;/h3&gt;
&lt;p&gt;This is where my long-standing interest in Linux and Bash became especially useful.&lt;/p&gt;
&lt;p&gt;I don't need to understand every internal detail of a service to start investigating it.&lt;/p&gt;
&lt;p&gt;But I do need to know how to find relevant information.&lt;/p&gt;
&lt;p&gt;Application logs, container logs, system services, network responses, and CI output can help connect the failure in the test to what actually happened in the system.&lt;/p&gt;
&lt;p&gt;The important skill isn't memorizing a hundred commands.&lt;/p&gt;
&lt;p&gt;It's knowing what question you're trying to answer.&lt;/p&gt;
&lt;p&gt;Did the request reach the service? Did the service reject it? Did a dependency fail? Was the process restarted? Was there an error at the same time as the test failure?&lt;/p&gt;
&lt;p&gt;When the timestamps and evidence line up, the investigation becomes much more useful.&lt;/p&gt;
&lt;h3 id=&quot;don-t-fix-flakiness-by-hiding-it&quot;&gt;Don't fix flakiness by hiding it&lt;/h3&gt;
&lt;p&gt;Flaky tests are frustrating, especially when they slow down a team.&lt;/p&gt;
&lt;p&gt;But adding retries or longer waits without understanding the cause can make the situation worse.&lt;/p&gt;
&lt;p&gt;Sometimes a retry is a reasonable mitigation. But it should not become a substitute for investigating why the test is unreliable.&lt;/p&gt;
&lt;p&gt;Is the application genuinely slow? Is the test waiting for the wrong condition? Is shared test data causing interference? Are tests running in parallel without proper isolation? Is the environment overloaded?&lt;/p&gt;
&lt;p&gt;A test that passes on the third attempt may still be telling you something important.&lt;/p&gt;
&lt;p&gt;The question is whether you understand what that something is.&lt;/p&gt;
&lt;h3 id=&quot;test-data-deserves-more-attention-than-it-gets&quot;&gt;Test data deserves more attention than it gets&lt;/h3&gt;
&lt;p&gt;Test data is another common source of misleading failures.&lt;/p&gt;
&lt;p&gt;An account might already exist. A record may have been modified by another test. An external system may have changed state. A cleanup step may not have run.&lt;/p&gt;
&lt;p&gt;A test can be technically correct and still fail because its assumptions about data are no longer true.&lt;/p&gt;
&lt;p&gt;This is why I increasingly value predictable setup, isolation, cleanup, and clear ownership of test data.&lt;/p&gt;
&lt;p&gt;Reliable automation isn't only about good assertions.&lt;/p&gt;
&lt;p&gt;It's also about controlling the conditions under which those assertions run.&lt;/p&gt;
&lt;h3 id=&quot;look-at-the-change-that-happened-before-the-failure&quot;&gt;Look at the change that happened before the failure&lt;/h3&gt;
&lt;p&gt;When a previously stable test starts failing, I want to know what changed.&lt;/p&gt;
&lt;p&gt;A new commit? A dependency update? A deployment? A configuration change? A database migration? A new feature flag?&lt;/p&gt;
&lt;p&gt;The recent change isn't automatically the cause, but it provides useful context.&lt;/p&gt;
&lt;p&gt;This is also where understanding CI/CD becomes valuable for QA.&lt;/p&gt;
&lt;p&gt;A pipeline isn't just a place where tests run. It contains information about the build, dependencies, environment, and changes that produced the result.&lt;/p&gt;
&lt;p&gt;The more of that context you can read, the less you have to guess.&lt;/p&gt;
&lt;h3 id=&quot;a-useful-investigation-produces-more-than-fixed-&quot;&gt;A useful investigation produces more than “fixed”&lt;/h3&gt;
&lt;p&gt;Eventually, the failure is resolved.&lt;/p&gt;
&lt;p&gt;Maybe it was an application bug. Maybe the test needed a better wait condition. Maybe a service was unavailable. Maybe the data setup was unreliable.&lt;/p&gt;
&lt;p&gt;But I think a good investigation should leave behind something more useful than a green rerun.&lt;/p&gt;
&lt;p&gt;What was the actual cause? How did we confirm it? Could the same failure happen again? Can we improve the test, environment, monitoring, or process so it's easier to diagnose next time?&lt;/p&gt;
&lt;p&gt;A failure is an opportunity to improve the system's ability to explain itself.&lt;/p&gt;
&lt;p&gt;That matters to QA, DevOps, and everyone responsible for reliable delivery.&lt;/p&gt;
&lt;h3 id=&quot;the-broader-lesson&quot;&gt;The broader lesson&lt;/h3&gt;
&lt;p&gt;The deeper I moved into automation, the more I realized that debugging requires an expanding field of view.&lt;/p&gt;
&lt;p&gt;Sometimes the answer is in the test.&lt;/p&gt;
&lt;p&gt;Sometimes it's in the application.&lt;/p&gt;
&lt;p&gt;Sometimes it's in the environment, CI, infrastructure, or deployment history.&lt;/p&gt;
&lt;p&gt;You don't need to be an expert in every layer to investigate effectively.&lt;/p&gt;
&lt;p&gt;But you do need to be willing to look beyond the layer where the failure first appeared.&lt;/p&gt;
&lt;p&gt;A red test is useful evidence.&lt;/p&gt;
&lt;p&gt;It tells us something didn't go as expected.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The real engineering work is finding out why.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This is the second article in my &lt;strong&gt;From QA to DevOps / Release Engineering&lt;/strong&gt; series.&lt;/p&gt;
&lt;p&gt;When an automated test fails in your team, what's the first thing you check outside the test report?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26540/a-failed-test-is-a-clue-not-a-diagnosis</guid>
<pubDate>Wed, 09 Sep 2026 04:57:38 +0000</pubDate>
</item>
<item>
<title>Canary Releases: How to Stop Deploying Broken Code to Everyone</title>
<link>https://coderlegion.com/25262/canary-releases-how-to-stop-deploying-broken-code-to-everyone</link>
<description>&lt;h3 id=&quot;what-is-a-canary-release-&quot;&gt;What is a Canary Release?&lt;/h3&gt;
&lt;p&gt;A canary release is a modern software deployment strategy where a newly developed version of an application is rolled out to a tiny fraction of real users before making it available to the entire user base. This technique serves as an invaluable safety mechanism to identify bugs, performance degradations, or architectural errors under real-world conditions without risking a widespread outage. If the update proves stable for this initial cohort, it is progressively rolled out to everyone else; otherwise, it is immediately rolled back with near-zero impact on the general public.&lt;/p&gt;
&lt;h3 id=&quot;the-canary-in-the-coal-mine&quot;&gt;The Canary in the Coal Mine&lt;/h3&gt;
&lt;p&gt;Historically, this concept stems from a crucial mining practice: coal miners would carry a live canary down into the deep subterranean tunnels because the birds are far more sensitive to toxic, odorless gases than humans are. If the canary stopped singing or collapsed, the miners knew they had to evacuate immediately before they were affected. In the modern tech world, our &quot;canary&quot; is the tiny, targeted group of active users who receive the new code version first. If their software sessions begin throwing errors, crashing, or slowing down, developers receive an immediate alert and can quickly shut down the test, preventing any damage from reaching the larger population.&lt;/p&gt;
&lt;h3 id=&quot;why-canary-releases-matter-in-daily-operations&quot;&gt;Why Canary Releases Matter in Daily Operations&lt;/h3&gt;
&lt;p&gt;In the daily lives of software engineers, canary releases are the difference between a peaceful workday and a high-severity, multi-hour outage. When deploying updates to systems with millions of active users, traditional &quot;big bang&quot; deployments—where 100% of the servers are updated simultaneously—are incredibly dangerous. No matter how much testing is done on offline staging environments, real-world user behavior and high network volumes cannot be perfectly simulated. By routing just 1% to 5% of live traffic to the new update, engineers can closely monitor system metrics like API response times, database query speeds, and error logs. If the canary group encounters an issue, only a tiny sliver of users is affected, saving the business from catastrophic financial and reputational damage.&lt;/p&gt;
&lt;h3 id=&quot;code-implementation-traffic-splitting&quot;&gt;Code Implementation: Traffic Splitting&lt;/h3&gt;
&lt;p&gt;Here is a simple Node.js-style routing mechanism that shows how traffic can be split programmatically based on a user's unique ID:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Deterministic canary router based on user ID hashes
function getAppVersionForUser(userId) {
  const CANARY_PERCENTAGE = 5; // Route 5% of users to canary
  
  // Generate a stable hash value between 0 and 99 based on the user ID
  const userBucket = Math.abs(userId) % 100;
  
  if (userBucket &amp;lt; CANARY_PERCENTAGE) {
    return &quot;v2.0.0-canary&quot;; // The new, experimental version
  }
  
  return &quot;v1.1.0-stable&quot;; // The reliable, existing version
}

// Example usage:
const userA = { id: 104 }; // 104 % 100 = 4 (Canary)
const userB = { id: 250 }; // 250 % 100 = 50 (Stable)

console.log(getAppVersionForUser(userA.id)); // Outputs: v2.0.0-canary
console.log(getAppVersionForUser(userB.id)); // Outputs: v1.1.0-stable
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;key-takeaway&quot;&gt;Key Takeaway&lt;/h3&gt;
&lt;p&gt;Ultimately, canary releases remove the fear and uncertainty from the deployment pipeline. By transforming launches from high-stakes, stressful events into gradual, data-driven experiments, development teams can safely ship new code multiple times a day while maintaining robust, uninterrupted uptime for their global customer base.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;resources&quot;&gt;Resources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Saurav-TB-Pandey/react-hook-lab&quot;&gt;react-hook-lab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;react-hook-lab:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/react-hook-lab&quot;&gt;npm package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connect with me on LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/pandeysaurav/&quot;&gt;Saurav Pandey&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/08/the-safe-way-to-deploy-software-updates.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/saurav_tb_pandey/canary-releases-how-to-stop-deploying-broken-code-to-everyone-c03&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25262/canary-releases-how-to-stop-deploying-broken-code-to-everyone</guid>
<pubDate>Tue, 01 Sep 2026 23:35:23 +0000</pubDate>
</item>
<item>
<title>Who Actually Owns the Release Decision?</title>
<link>https://coderlegion.com/25600/who-actually-owns-the-release-decision</link>
<description>&lt;p&gt;The pipeline is green.&lt;/p&gt;
&lt;p&gt;QA signed off.&lt;/p&gt;
&lt;p&gt;The deployment window is open.&lt;/p&gt;
&lt;p&gt;Someone asks:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Are we good to release?”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And then something interesting happens.&lt;/p&gt;
&lt;p&gt;Everyone looks at someone else.&lt;/p&gt;
&lt;p&gt;The developer knows the code.&lt;/p&gt;
&lt;p&gt;QA knows what was tested.&lt;/p&gt;
&lt;p&gt;DevOps knows whether the deployment can run.&lt;/p&gt;
&lt;p&gt;Security knows about the findings.&lt;/p&gt;
&lt;p&gt;The product owner understands the business impact.&lt;/p&gt;
&lt;p&gt;But who actually owns the final decision?&lt;/p&gt;
&lt;p&gt;In many teams, the answer is surprisingly unclear.&lt;/p&gt;
&lt;p&gt;And I think that ambiguity is becoming a bigger problem as software delivery gets more automated.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;release-ownership-is-often-implicit&quot;&gt;Release ownership is often implicit&lt;/h3&gt;
&lt;p&gt;Most teams have a release process.&lt;/p&gt;
&lt;p&gt;Far fewer have a clearly defined release decision process.&lt;/p&gt;
&lt;p&gt;There is usually some combination of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CI checks&lt;/li&gt;
&lt;li&gt;automated tests&lt;/li&gt;
&lt;li&gt;QA approval&lt;/li&gt;
&lt;li&gt;pull request reviews&lt;/li&gt;
&lt;li&gt;security scans&lt;/li&gt;
&lt;li&gt;deployment jobs&lt;/li&gt;
&lt;li&gt;Slack conversations&lt;/li&gt;
&lt;li&gt;change tickets&lt;/li&gt;
&lt;li&gt;someone saying “looks good to me”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Eventually, the release happens.&lt;/p&gt;
&lt;p&gt;But if you ask:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Why was this release considered safe enough to ship?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;the answer is often reconstructed afterwards.&lt;/p&gt;
&lt;p&gt;Maybe the pipeline was green.&lt;/p&gt;
&lt;p&gt;Maybe QA approved it.&lt;/p&gt;
&lt;p&gt;Maybe an engineering lead gave a thumbs up.&lt;/p&gt;
&lt;p&gt;Maybe nobody saw a reason to stop it.&lt;/p&gt;
&lt;p&gt;That last one is more common than we probably like to admit.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-nobody-stopped-it-is-not-a-release-decision&quot;&gt;“Nobody stopped it” is not a release decision&lt;/h3&gt;
&lt;p&gt;There is an important difference between:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Nothing blocked the release.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;and:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;We evaluated the available evidence and decided the release risk was acceptable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The first is passive.&lt;/p&gt;
&lt;p&gt;The second is a decision.&lt;/p&gt;
&lt;p&gt;Many delivery systems are designed around the first model.&lt;/p&gt;
&lt;p&gt;A release moves forward unless something fails.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build       PASS
Tests       PASS
Security    PASS
Approval    PASS

            ↓

          DEPLOY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That works well when the important risks can be represented as binary checks.&lt;/p&gt;
&lt;p&gt;But releases are rarely that simple.&lt;/p&gt;
&lt;p&gt;Imagine this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Tests                     PASS
Security scan             PASS
Build                     PASS

Database migration        YES
Authentication change     YES
Rollback confidence       MEDIUM
Observability coverage    PARTIAL
Deployment timing         PEAK HOURS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing necessarily failed.&lt;/p&gt;
&lt;p&gt;But someone still needs to decide whether those signals add up to an acceptable release.&lt;/p&gt;
&lt;p&gt;That is not execution.&lt;/p&gt;
&lt;p&gt;That is judgment.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;different-people-own-different-pieces-of-the-truth&quot;&gt;Different people own different pieces of the truth&lt;/h3&gt;
&lt;p&gt;One reason release ownership becomes fuzzy is that no single person has all the information.&lt;/p&gt;
&lt;p&gt;A developer might know:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“The implementation is small and well understood.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;QA might know:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“The regression suite passed, but this integration wasn’t fully covered.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Security might know:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“There is a finding, but it is low severity and unrelated to this change.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Operations might know:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Rollback is technically possible, but it hasn’t been tested recently.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The product team might know:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“This release needs to go out today because it fixes an important customer issue.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Each perspective is valid.&lt;/p&gt;
&lt;p&gt;None of them alone represents the whole release.&lt;/p&gt;
&lt;p&gt;That means the problem isn’t simply finding the right person to press the button.&lt;/p&gt;
&lt;p&gt;The real problem is bringing the evidence together before that decision is made.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;approval-and-decision-are-not-the-same-thing&quot;&gt;Approval and decision are not the same thing&lt;/h3&gt;
&lt;p&gt;We often treat approvals as if they solve this problem.&lt;/p&gt;
&lt;p&gt;They help, but an approval is only useful if we understand what is being approved.&lt;/p&gt;
&lt;p&gt;Consider a typical approval step:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Production deployment waiting for approval.

Approve / Reject
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What exactly does “Approve” mean?&lt;/p&gt;
&lt;p&gt;Did the approver review the test results?&lt;/p&gt;
&lt;p&gt;Did they see the security findings?&lt;/p&gt;
&lt;p&gt;Do they know that the release contains a database migration?&lt;/p&gt;
&lt;p&gt;Did they check rollback readiness?&lt;/p&gt;
&lt;p&gt;Do they know whether the change affects authentication or payments?&lt;/p&gt;
&lt;p&gt;Or are they simply approving because the previous pipeline stages are green?&lt;/p&gt;
&lt;p&gt;An approval button does not automatically create a meaningful decision process.&lt;/p&gt;
&lt;p&gt;Sometimes it just moves responsibility from automation to a human without giving that human better information.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;humans-should-make-decisions-not-collect-evidence-manually&quot;&gt;Humans should make decisions, not collect evidence manually&lt;/h3&gt;
&lt;p&gt;This becomes especially important when teams introduce human approval gates.&lt;/p&gt;
&lt;p&gt;The common pattern is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Automation runs
      ↓
Human approval
      ↓
Deployment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But the human often has to investigate everything manually.&lt;/p&gt;
&lt;p&gt;Open the CI pipeline.&lt;/p&gt;
&lt;p&gt;Check the test report.&lt;/p&gt;
&lt;p&gt;Look at the pull request.&lt;/p&gt;
&lt;p&gt;Check security.&lt;/p&gt;
&lt;p&gt;Search Slack.&lt;/p&gt;
&lt;p&gt;Ask whether staging was tested.&lt;/p&gt;
&lt;p&gt;Find the deployment notes.&lt;/p&gt;
&lt;p&gt;Then make a decision.&lt;/p&gt;
&lt;p&gt;At that point the human is not really acting as a decision-maker.&lt;/p&gt;
&lt;p&gt;They are acting as an evidence aggregator.&lt;/p&gt;
&lt;p&gt;And that is exactly the part automation should help with.&lt;/p&gt;
&lt;p&gt;A better model might look like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CI / QA / Security / Change / Operations
                   ↓
             Evidence layer
                   ↓
              Risk context
                   ↓
             Human decision
                   ↓
                Release
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Automation gathers and structures the evidence.&lt;/p&gt;
&lt;p&gt;Humans apply judgment where judgment is actually needed.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;not-every-release-needs-the-same-level-of-control&quot;&gt;Not every release needs the same level of control&lt;/h3&gt;
&lt;p&gt;Another problem with traditional release gates is that they are often static.&lt;/p&gt;
&lt;p&gt;Every release follows the same workflow.&lt;/p&gt;
&lt;p&gt;But risk is not static.&lt;/p&gt;
&lt;p&gt;Compare these two changes.&lt;/p&gt;
&lt;h4 id=&quot;release-a-1&quot;&gt;Release A&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Copy change
No dependency updates
No database changes
Full regression passed
Easy rollback
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;release-b-1&quot;&gt;Release B&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Authentication refactor
Database migration
New external dependency
Partial regression
Rollback requires data restoration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Should they really require exactly the same release process?&lt;/p&gt;
&lt;p&gt;Probably not.&lt;/p&gt;
&lt;p&gt;The first may need very little additional review.&lt;/p&gt;
&lt;p&gt;The second probably deserves significantly more evidence and possibly explicit approval.&lt;/p&gt;
&lt;p&gt;This is why I think release processes should become more &lt;strong&gt;risk-aware&lt;/strong&gt; rather than simply adding more mandatory gates.&lt;/p&gt;
&lt;p&gt;The question shouldn’t be:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Did this release complete all 12 steps?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It should be:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Given the risk of this particular change, do we have enough evidence to release it?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-release-decision-should-be-explainable&quot;&gt;A release decision should be explainable&lt;/h3&gt;
&lt;p&gt;There is another useful test.&lt;/p&gt;
&lt;p&gt;After a release, could someone explain why it was approved?&lt;/p&gt;
&lt;p&gt;Not:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Because the pipeline was green.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;But something closer to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Decision: GO

Why:
- regression suite passed
- no critical security findings
- affected service has healthy staging metrics
- rollback artifact is available
- database migration validated
- required approval received
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or perhaps:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Decision: CONDITIONAL GO

Why:
- automated checks passed
- minor observability gap remains
- release approved with enhanced monitoring
- rollback owner assigned
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And sometimes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Decision: NO GO

Why:
- critical workflow not covered
- rollback path unverified
- production dependency currently unstable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That creates something extremely valuable:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;decision history.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Not just what happened.&lt;/p&gt;
&lt;p&gt;But why.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;release-history-usually-tells-us-what-happened&quot;&gt;Release history usually tells us what happened&lt;/h3&gt;
&lt;p&gt;Most engineering systems are already very good at recording events.&lt;/p&gt;
&lt;p&gt;We can usually find:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;which commit was deployed&lt;/li&gt;
&lt;li&gt;which pipeline ran&lt;/li&gt;
&lt;li&gt;which tests passed&lt;/li&gt;
&lt;li&gt;who merged the pull request&lt;/li&gt;
&lt;li&gt;when the deployment started&lt;/li&gt;
&lt;li&gt;whether the deployment succeeded&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That is useful.&lt;/p&gt;
&lt;p&gt;But during an incident, audit, or retrospective, another question quickly appears:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Why did we believe this release was safe enough to ship?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That answer is often much harder to reconstruct.&lt;/p&gt;
&lt;p&gt;The evidence may still exist, but it is scattered across multiple systems.&lt;/p&gt;
&lt;p&gt;A CI run.&lt;/p&gt;
&lt;p&gt;A test report.&lt;/p&gt;
&lt;p&gt;A security scanner.&lt;/p&gt;
&lt;p&gt;A ticket.&lt;/p&gt;
&lt;p&gt;A Slack message.&lt;/p&gt;
&lt;p&gt;Someone’s memory.&lt;/p&gt;
&lt;p&gt;The release happened because all of those signals somehow formed a decision.&lt;/p&gt;
&lt;p&gt;But the decision itself was never captured.&lt;/p&gt;
&lt;p&gt;That feels like an important gap.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;this-matters-even-more-with-ai-assisted-development&quot;&gt;This matters even more with AI-assisted development&lt;/h3&gt;
&lt;p&gt;Software creation is accelerating.&lt;/p&gt;
&lt;p&gt;AI can generate code.&lt;/p&gt;
&lt;p&gt;AI can generate tests.&lt;/p&gt;
&lt;p&gt;AI can review pull requests.&lt;/p&gt;
&lt;p&gt;AI can fix pipeline failures.&lt;/p&gt;
&lt;p&gt;AI agents are increasingly capable of performing parts of the delivery workflow themselves.&lt;/p&gt;
&lt;p&gt;Eventually, much of the mechanical software delivery process may become highly automated.&lt;/p&gt;
&lt;p&gt;That makes one question increasingly important:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;What should remain a decision?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The answer probably isn’t:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Everything must be manually approved.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That would destroy much of the value of automation.&lt;/p&gt;
&lt;p&gt;But the answer also shouldn’t be:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The pipeline passed, therefore release.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As automation increases, teams will need to become much more explicit about the boundary between:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;execution&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;authority.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Machines are excellent at collecting signals, evaluating rules, and enforcing policy.&lt;/p&gt;
&lt;p&gt;Humans are still valuable when context, uncertainty, trade-offs, and business risk matter.&lt;/p&gt;
&lt;p&gt;The interesting challenge is designing the boundary between the two.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-decision-should-depend-on-risk&quot;&gt;The decision should depend on risk&lt;/h3&gt;
&lt;p&gt;I don’t think every release needs a human standing in front of a giant approval button.&lt;/p&gt;
&lt;p&gt;That would simply create another bottleneck.&lt;/p&gt;
&lt;p&gt;Low-risk releases should be able to move quickly.&lt;/p&gt;
&lt;p&gt;If the evidence is strong, the change is small, rollback is easy, and the affected systems are well understood, automation may be perfectly capable of approving the release automatically.&lt;/p&gt;
&lt;p&gt;But as risk increases, the decision process can change.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;LOW RISK
    ↓
Evidence sufficient
    ↓
Automatic GO
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A more complex release might look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;MEDIUM RISK
     ↓
Evidence sufficient
     ↓
Conditional approval
     ↓
Human review
     ↓
GO
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And a high-risk release might produce:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;HIGH RISK
    ↓
Missing critical evidence
    ↓
NO GO
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important part is that the decision is based on the release context.&lt;/p&gt;
&lt;p&gt;Not simply on whether a static checklist turned green.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;maybe-nobody-should-own-the-release-alone&quot;&gt;Maybe nobody should “own” the release alone&lt;/h3&gt;
&lt;p&gt;This leads to a slightly different way of thinking about release ownership.&lt;/p&gt;
&lt;p&gt;Perhaps the release decision should not belong entirely to one person or one tool.&lt;/p&gt;
&lt;p&gt;Instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Engineering provides change evidence

QA provides quality evidence

Security provides security evidence

Operations provides deployment evidence

Automation evaluates policy

Humans resolve uncertainty
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the final release decision becomes the result of that system.&lt;/p&gt;
&lt;p&gt;Not one person’s intuition.&lt;/p&gt;
&lt;p&gt;Not one green pipeline.&lt;/p&gt;
&lt;p&gt;Not one approval button.&lt;/p&gt;
&lt;p&gt;A decision backed by evidence.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-release-button-is-the-easy-part&quot;&gt;The release button is the easy part&lt;/h3&gt;
&lt;p&gt;We have become very good at automating deployment.&lt;/p&gt;
&lt;p&gt;Running containers, updating infrastructure, promoting artifacts, and rolling out applications can now happen in seconds.&lt;/p&gt;
&lt;p&gt;Modern delivery systems are extremely good at answering:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;How do we deploy this?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The harder question comes immediately before that:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Should we deploy this?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And if the answer is yes:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Why are we confident enough to do it?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is a very different problem.&lt;/p&gt;
&lt;p&gt;It combines engineering signals, quality evidence, operational context, security information, policy, and sometimes human judgment.&lt;/p&gt;
&lt;p&gt;I think that question is going to become one of the most important parts of modern software delivery.&lt;/p&gt;
&lt;p&gt;Especially as everything around it becomes increasingly automated.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;So maybe the real question isn’t:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Who owns the release button?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Who — or what — owns the release decision?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And more importantly:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can that decision be explained afterwards?&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;How does your team handle this today?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Who actually owns the decision that a release is ready to go?&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25600/who-actually-owns-the-release-decision</guid>
<pubDate>Fri, 28 Aug 2026 07:16:54 +0000</pubDate>
</item>
<item>
<title>MLOps for Engineers: How to Take an AI Model from Jupyter to Production Without Dying in the Attempt</title>
<link>https://coderlegion.com/25566/mlops-for-engineers-how-take-an-ai-model-from-jupyter-production-without-dying-the-attempt</link>
<description>&lt;p&gt;There is a statistic from Gartner that every data team knows and most prefer to ignore: &lt;strong&gt;87% of Machine Learning and AI projects never make it to production&lt;/strong&gt;. They remain trapped in &quot;prototype limbo&quot;: a Jupyter notebook on a data scientist's laptop boasting an impressive 96% accuracy over a static CSV file from 2023, but which no one knows how to deploy, version, update, or monitor within an enterprise's live infrastructure.&lt;/p&gt;
&lt;p&gt;The reason for this widespread failure is neither mathematical nor algorithmic. It's not a matter of needing more hyperparameters to tune or deeper neural network layers to stack. &lt;strong&gt;It is a software engineering and operations failure&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;As demonstrated in Google's landmark paper &lt;em&gt;&quot;Hidden Technical Debt in Machine Learning Systems&quot;&lt;/em&gt; (Sculley et al.), the actual ML model code accounts for a mere &lt;strong&gt;5% to 10%&lt;/strong&gt; of the entire production system. The remaining 90% is infrastructure: data extraction and verification, dependency management, automated retraining, artifact versioning, observability against the dreaded &lt;em&gt;Data Drift&lt;/em&gt;, and continuous governance.&lt;/p&gt;
&lt;p&gt;This integrative discipline is called &lt;strong&gt;MLOps&lt;/strong&gt; (&lt;em&gt;Machine Learning Operations&lt;/em&gt;). And after building, deploying, and operating real production pipelines on this blog — from demand forecasting with Prophet in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/sop-engineering-part2-forecasting/&quot;&gt;S&amp;amp;OP series&lt;/a&gt; to autonomous agents in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/ai_agents_part1/&quot;&gt;Autopilot series&lt;/a&gt; and the &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/obs_part5_radar_agent/&quot;&gt;Obsolescence Radar&lt;/a&gt; —, this article is the practical guide that distills the journey from a sandbox script to an industrial production system.&lt;/p&gt;
&lt;h4 id=&quot;anatomy-of-the-problem-why-traditional-software-engineering-falls-short-with-ml&quot;&gt;Anatomy of the Problem: Why Traditional Software Engineering Falls Short with ML&lt;/h4&gt;
&lt;p&gt;In traditional software development (DevOps), system behavior depends entirely on &lt;strong&gt;source code&lt;/strong&gt;. If you write a deterministic function, test it with unit tests, and deploy it via CI/CD, the system behaves predictably as long as the underlying infrastructure remains healthy.&lt;/p&gt;
&lt;p&gt;In Machine Learning and generative AI systems, behavior depends upon an interdependent trinity: &lt;strong&gt;Code + Data + Model&lt;/strong&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Code may remain unchanged&lt;/strong&gt;, yet if the statistical distribution of real-world data shifts (which happens constantly across supply chains, financial markets, and customer behaviors), &lt;strong&gt;model performance degrades silently&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reproducibility is non-trivial&lt;/strong&gt;: retraining the exact same Python script with today's data produces a completely different binary artifact than last week's run.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Failures don't throw an HTTP 500 error&lt;/strong&gt;: a production model doesn't crash like a web server; it simply begins serving garbage predictions with 99% statistical confidence.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To keep your system from becoming an uncontrollable black box, your architecture must stand upon four fundamental pillars.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://datalaria.com/en/posts/mlops_for_engineers/mlops_pillars.jpg&quot; alt=&quot;The 4 fundamental pillars of the continuous MLOps lifecycle&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;pillar-1-comprehensive-versioning-code-data-and-models-&quot;&gt;Pillar 1: Comprehensive Versioning (Code, Data, and Models)&lt;/h4&gt;
&lt;p&gt;If you cannot recreate the exact environment, data snapshot, and parameters that produced a prediction six months ago, your system is neither reproducible nor auditable. In regulated enterprise environments, this is not merely good engineering hygiene; it is a strict legal requirement under the &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/eu_ai_act/&quot;&gt;EU AI Act&lt;/a&gt; (Article 12 on automated logging and traceability).&lt;/p&gt;
&lt;p&gt;Versioning in MLOps spans three distinct layers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Code Versioning&lt;/strong&gt;: Standard Git. Centralized repository with protected branches, pull request reviews, and version tags.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Versioning (DVC / Delta Lake)&lt;/strong&gt;: Git was never designed to store gigabyte- or terabyte-scale binary files. Tools like &lt;strong&gt;DVC&lt;/strong&gt; (&lt;em&gt;Data Version Control&lt;/em&gt;) create lightweight metadata pointer files versioned in Git, while actual datasets reside in object storage (Amazon S3, Google Cloud Storage, or Supabase Storage). This allows a simple &lt;code&gt;git checkout v1.2.0&lt;/code&gt; to restore both the training code and the exact data snapshot that fed it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model Registry (MLflow / Weights &amp;amp; Biases)&lt;/strong&gt;: A centralized catalog acting as the &quot;Docker Hub&quot; for trained models. Each registered model tracks serialized weights (ONNX, Pickle, Safetensors), hyperparameters, validation metrics, author, associated Git commit hash, and lifecycle stage (&lt;code&gt;Staging&lt;/code&gt;, &lt;code&gt;Production&lt;/code&gt;, &lt;code&gt;Archived&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;pillar-2-experiment-tracking-and-pipeline-automation&quot;&gt;Pillar 2: Experiment Tracking and Pipeline Automation&lt;/h4&gt;
&lt;p&gt;A data scientist's or ML engineer's workflow is inherently iterative. Testing dozens of feature permutations, model algorithms, Pandas transformations, and regularization parameters without systematic logging leads straight to chaos: scattered notebook files, models named &lt;code&gt;final_model_v2_really_final.pkl&lt;/code&gt;, and complete loss of institutional knowledge.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MLflow&lt;/strong&gt; and &lt;strong&gt;Weights &amp;amp; Biases (W&amp;amp;B)&lt;/strong&gt; solve this by systematically intercepting every training run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import mlflow
import mlflow.prophet
from prophet import Prophet

# Start experiment tracking
mlflow.set_experiment(&quot;sop_demand_forecasting&quot;)

with mlflow.start_run(run_name=&quot;prophet_multiplicative_v3&quot;):
    # Log configuration parameters
    params = {
        &quot;seasonality_mode&quot;: &quot;multiplicative&quot;,
        &quot;changepoint_prior_scale&quot;: 0.05,
        &quot;n_changepoints&quot;: 20
    }
    mlflow.log_params(params)
    
    # Train the model
    model = Prophet(**params)
    model.fit(train_df)
    
    # Evaluate and log performance metrics
    metrics = evaluate_forecast(model, test_df)
    mlflow.log_metrics({
        &quot;mape&quot;: metrics[&quot;mape&quot;],
        &quot;rmse&quot;: metrics[&quot;rmse&quot;],
        &quot;coverage_p95&quot;: metrics[&quot;coverage&quot;]
    })
    
    # Automatically register the artifact
    mlflow.prophet.log_model(model, artifact_path=&quot;prophet_model&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By structuring experiments with automated tracking, comparing 50 model iterations transforms from a guessing game into an instant analytical query on a unified dashboard.&lt;/p&gt;
&lt;h4 id=&quot;pillar-3-ci-cd-and-automated-deployment-ct-continuous-training-&quot;&gt;Pillar 3: CI/CD and Automated Deployment (CT: Continuous Training)&lt;/h4&gt;
&lt;p&gt;In MLOps, CI/CD expands to incorporate a vital third dimension: &lt;strong&gt;Continuous Training (CT)&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CI (Continuous Integration)&lt;/strong&gt;: Goes beyond validating Python syntax and running unit tests (&lt;code&gt;pytest&lt;/code&gt;, &lt;code&gt;flake8&lt;/code&gt;). It executes specialized data validation tests: verifying that incoming schemas match expectations, null values remain within acceptable tolerances, and feature ranges stay valid (using libraries like &lt;strong&gt;Great Expectations&lt;/strong&gt; or Pydantic).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CD (Continuous Delivery / Deployment)&lt;/strong&gt;: Packages the validated model into an optimized Docker container or a &lt;strong&gt;FastAPI&lt;/strong&gt; microservice (as showcased in &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/obs_part6_fastapi/&quot;&gt;Observability Part 6&lt;/a&gt;) and automatically deploys it upon passing regression test suites.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CT (Continuous Training)&lt;/strong&gt;: When production monitoring detects performance degradation or new data batches arrive, an automated pipeline (orchestrated via &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/ai_agents_part5/&quot;&gt;GitHub Actions&lt;/a&gt;, Prefect, or Airflow) retrains the model, verifies that new metrics surpass the baseline champion model, and automatically promotes the candidate to the Model Registry.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;pillar-4-production-observability-the-war-against-drift-&quot;&gt;Pillar 4: Production Observability (The War Against Drift)&lt;/h4&gt;
&lt;p&gt;Once your model is deployed and serving batch or real-time inferences, the real challenge begins. Models degrade over time due to two distinct mathematical phenomena:&lt;/p&gt;
&lt;h5 id=&quot;1-data-drift-covariate-shift-&quot;&gt;1. Data Drift (Covariate Shift)&lt;/h5&gt;
&lt;p&gt;The statistical distribution of input variables ($P(X)$) changes relative to the training distribution, even if the underlying relationship between inputs and outputs holds steady.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Real-world example&lt;/em&gt;: A demand forecasting model trained prior to a global supply chain disruption experiences a sudden surge in supplier lead times. The model receives valid numbers, but operates in a vector space region where it was never trained.&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&quot;2-concept-drift&quot;&gt;2. Concept Drift&lt;/h5&gt;
&lt;p&gt;The mathematical relationship between input features and target variables ($P(Y|X)$) shifts over time.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Real-world example&lt;/em&gt;: In customer purchasing predictions, a product historically bought primarily during winter becomes an all-season hit due to a social media trend. The inputs remain identical, but consumer behavior has fundamentally evolved.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# Data Drift detection example using Evidently AI
from evidently.report import Report
from evidently.metric_preset import DataDriftPreset

data_drift_report = Report(metrics=[DataDriftPreset()])
data_drift_report.run(reference_data=reference_df, current_data=production_df)

# If drift exceeds threshold, trigger automatic alerting &amp;amp; retraining
if data_drift_report.as_dict()[&quot;metrics&quot;][0][&quot;result&quot;][&quot;dataset_drift&quot;]:
    trigger_mlops_alert(&quot;DATA_DRIFT_DETECTED: Triggering automated retraining pipeline&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tools like &lt;strong&gt;Evidently AI&lt;/strong&gt; or Prometheus + Grafana allow data engineering teams to track statistical drift tests (such as Kolmogorov-Smirnov for numerical features or Chi-Square tests for categorical data) and alert engineers long before degraded accuracy causes financial losses.&lt;/p&gt;
&lt;h4 id=&quot;real-production-case-moving-prophet-from-notebook-to-s-amp-op-pipeline&quot;&gt;Real Production Case: Moving Prophet from Notebook to S&amp;amp;OP Pipeline&lt;/h4&gt;
&lt;p&gt;To see how MLOps applies in real industrial contexts, consider the evolution of our demand planning pipeline in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/sop-engineering-part2-forecasting/&quot;&gt;S&amp;amp;OP series&lt;/a&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Stage &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Initial Sandbox (Jupyter) &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Production Architecture (MLOps)&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;Data Ingestion&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Manual loading of static &lt;code&gt;sales_2023.csv&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Automated Supabase PostgreSQL sync with schema assertion&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Data Hygiene&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Manual &lt;code&gt;df.dropna()&lt;/code&gt; without validation &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Automated Z-Score outlier detection as detailed in &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/sop_engineering-data-hygiene/&quot;&gt;Data Hygiene&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Training&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Manual top-to-bottom cell execution &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Decoupled pipeline running weekly via GitHub Actions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Bayesian Inference&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Single point forecast &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Probabilistic confidence intervals directly driving Safety Stock calculations&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Consumption&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Static &lt;code&gt;plt.show()&lt;/code&gt; inline charts &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;FastAPI microservice feeding the PuLP linear optimization engine (&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/sop-engineering-part3-optimization/&quot;&gt;Part 3&lt;/a&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Monitoring&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;None &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Continuous weekly MAPE tracking triggering automated retraining&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This engineering transition transformed an isolated data analysis script into an &lt;strong&gt;automated, resilient, enterprise-grade system&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;from-mlops-to-llmops-the-agentic-frontier&quot;&gt;From MLOps to LLMOps: The Agentic Frontier&lt;/h4&gt;
&lt;p&gt;In 2026, the rise of LLMs and autonomous agent architectures hasn't eliminated the need for MLOps; it has evolved it into &lt;strong&gt;LLMOps&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When designing multi-agent workflows like those in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/ai_agents_part1/&quot;&gt;Autopilot series&lt;/a&gt; or navigating &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/finetuning_vs_rag/&quot;&gt;Fine-Tuning vs Prompt Engineering vs RAG&lt;/a&gt;, operational challenges take on fresh complexities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Automated Prompt CI/CD Regression Testing&lt;/strong&gt;: Adjusting an agent's system prompt can fix one edge case while subtly breaking three others. LLMOps pipelines run automated regression test suites against curated evaluation datasets using frameworks like &lt;strong&gt;RAGAS&lt;/strong&gt; or DeepEval.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Token Cost and Latency Observability&lt;/strong&gt;: As discussed in &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/hidden_economics_ai/&quot;&gt;The Hidden Economics of AI&lt;/a&gt;, token consumption and inference latency are mission-critical operational metrics that must be monitored just as closely as server CPU or memory utilization.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Proactive Security Governance&lt;/strong&gt;: Given vulnerabilities like &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/prompt_injection/&quot;&gt;Prompt Injection&lt;/a&gt;, LLMOps observability must continuously audit tool execution payloads (Tool Calling / MCP) and flag anomalous behaviors before they trigger unauthorized downstream actions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-eu-ai-act-connection-compliance-as-code&quot;&gt;The EU AI Act Connection: Compliance as Code&lt;/h4&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/eu_ai_act/&quot;&gt;EU AI Act&lt;/a&gt; has elevated MLOps from a best-practice engineering recommendation to a &lt;strong&gt;mandatory legal requirement&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Article 9 (Risk Management System)&lt;/strong&gt;: Mandates continuous risk identification and mitigation across the entire AI lifecycle (fulfilled via automated CI/CD and continuous testing).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Article 12 (Record-Keeping &amp;amp; Logging)&lt;/strong&gt;: Requires automated event logging throughout production operations (fulfilled via Model Registries and observability tracking).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Article 15 (Accuracy, Robustness &amp;amp; Cybersecurity)&lt;/strong&gt;: Demands that high-risk systems maintain consistent accuracy benchmarks and resist adversarial manipulation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Implementing rigorous MLOps practices allows companies to satisfy these regulatory mandates natively through infrastructure-as-code, eliminating the friction and overhead of retrospective audits.&lt;/p&gt;
&lt;h4 id=&quot;conclusion-mlops-is-the-maturity-of-artificial-intelligence&quot;&gt;Conclusion: MLOps is the Maturity of Artificial Intelligence&lt;/h4&gt;
&lt;p&gt;Anyone can clone a repository, open a Jupyter notebook, and fit a model in three lines of Python. But real engineering is not about making an algorithm work once in a sanitized sandbox; it is about &lt;strong&gt;ensuring it runs reliably, accurately, and securely ten thousand times a day in production&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The continuous MLOps loop — design, train, validate, deploy, monitor, and retrain — is the purest technological expression of the PDCA (&lt;em&gt;Plan-Do-Check-Adjust&lt;/em&gt;) cycle that &lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/deming/&quot;&gt;W. Edwards Deming&lt;/a&gt; pioneered for industrial quality excellence.&lt;/p&gt;
&lt;p&gt;If you aspire to build AI systems that generate enduring enterprise value, leave behind the comfort of the solitary notebook and embrace the discipline of MLOps. Your future self — and your on-call engineering team — will thank you forever.&lt;/p&gt;
&lt;hr&gt;
&lt;h5 id=&quot;sources-of-interest--1&quot;&gt;Sources of Interest:&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://research.google/pubs/pub43146/&quot;&gt;&lt;strong&gt;Google Research&lt;/strong&gt;: Hidden Technical Debt in Machine Learning Systems (Sculley et al.)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://mlflow.org/&quot;&gt;&lt;strong&gt;MLflow&lt;/strong&gt;: Open Source Platform for the Machine Learning Lifecycle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dvc.org/&quot;&gt;&lt;strong&gt;DVC (Data Version Control)&lt;/strong&gt;: Data &amp;amp; Model Versioning for ML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.evidentlyai.com/&quot;&gt;&lt;strong&gt;Evidently AI&lt;/strong&gt;: Open-Source ML Model Monitoring and Drift Detection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/sop-engineering-part2-forecasting/&quot;&gt;&lt;strong&gt;Datalaria&lt;/strong&gt;: S&amp;amp;OP Part 2 — Demand Planning with Prophet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/sop-engineering-part3-optimization/&quot;&gt;&lt;strong&gt;Datalaria&lt;/strong&gt;: S&amp;amp;OP Part 3 — Linear Optimization with PuLP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/sop_engineering-data-hygiene/&quot;&gt;&lt;strong&gt;Datalaria&lt;/strong&gt;: S&amp;amp;OP Data Hygiene for Industrial Pipelines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/finetuning_vs_rag/&quot;&gt;&lt;strong&gt;Datalaria&lt;/strong&gt;: Fine-Tuning vs Prompt Engineering vs RAG&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/prompt_injection/&quot;&gt;&lt;strong&gt;Datalaria&lt;/strong&gt;: Prompt Injection — AI Security and Agent Vulnerabilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/eu_ai_act/&quot;&gt;&lt;strong&gt;Datalaria&lt;/strong&gt;: EU AI Act — Engineering Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://datalaria.com/en/posts/deming/&quot;&gt;&lt;strong&gt;Datalaria&lt;/strong&gt;: W. Edwards Deming — Total Quality and the PDCA Cycle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25566/mlops-for-engineers-how-take-an-ai-model-from-jupyter-production-without-dying-the-attempt</guid>
<pubDate>Thu, 27 Aug 2026 19:08:27 +0000</pubDate>
</item>
<item>
<title>Your Tests Passed. That Still Doesn’t Mean You Should Release.</title>
<link>https://coderlegion.com/25305/your-tests-passed-that-still-doesnt-mean-you-should-release</link>
<description>&lt;p&gt;Your Tests Passed. That Still Doesn’t Mean You Should Release.&lt;/p&gt;
&lt;p&gt;A green test suite feels reassuring.&lt;/p&gt;
&lt;p&gt;Unit tests pass. Integration tests pass. End-to-end tests pass. CI is green.&lt;/p&gt;
&lt;p&gt;At that point, releasing can feel like the obvious next step.&lt;/p&gt;
&lt;p&gt;But there is a problem:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passing tests prove that the checks you wrote passed. They do not prove that the release is a good decision.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That distinction matters more as systems become larger, deployments become more frequent, and release pipelines become increasingly automated.&lt;/p&gt;
&lt;h3 id=&quot;tests-answer-a-narrow-question&quot;&gt;Tests answer a narrow question&lt;/h3&gt;
&lt;p&gt;A test tells you whether a specific expectation was satisfied.&lt;/p&gt;
&lt;p&gt;A unit test might tell you that a function returned the expected value.&lt;/p&gt;
&lt;p&gt;An API test might confirm that an endpoint returned &lt;code&gt;200&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;An end-to-end test might prove that a customer can complete checkout.&lt;/p&gt;
&lt;p&gt;These are valuable signals.&lt;/p&gt;
&lt;p&gt;But every test is built around assumptions.&lt;/p&gt;
&lt;p&gt;Someone decided:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what should be tested,&lt;/li&gt;
&lt;li&gt;what data should be used,&lt;/li&gt;
&lt;li&gt;which environments matter,&lt;/li&gt;
&lt;li&gt;which failure modes are important,&lt;/li&gt;
&lt;li&gt;and what should be ignored.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A passing test therefore means:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Under the conditions we tested, the expected behavior occurred.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; automatically mean:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;This version is safe to release.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Those are two different statements.&lt;/p&gt;
&lt;h3 id=&quot;a-release-contains-more-evidence-than-test-results&quot;&gt;A release contains more evidence than test results&lt;/h3&gt;
&lt;p&gt;Imagine a deployment where every automated test passes.&lt;/p&gt;
&lt;p&gt;At the same time:&lt;/p&gt;
&lt;p&gt;A dependency was upgraded.&lt;/p&gt;
&lt;p&gt;A database migration touches a large production table.&lt;/p&gt;
&lt;p&gt;Error rates increased slightly during staging.&lt;/p&gt;
&lt;p&gt;A feature flag configuration changed.&lt;/p&gt;
&lt;p&gt;The deployment modifies authentication logic.&lt;/p&gt;
&lt;p&gt;Observability coverage for the new code is incomplete.&lt;/p&gt;
&lt;p&gt;None of these conditions necessarily cause a test failure.&lt;/p&gt;
&lt;p&gt;Yet all of them may affect the release decision.&lt;/p&gt;
&lt;p&gt;This is where software teams often confuse &lt;strong&gt;verification&lt;/strong&gt; with &lt;strong&gt;decision-making&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Tests verify behavior.&lt;/p&gt;
&lt;p&gt;Release decisions evaluate risk.&lt;/p&gt;
&lt;h3 id=&quot;the-dangerous-assumption-behind-green-pipelines&quot;&gt;The dangerous assumption behind green pipelines&lt;/h3&gt;
&lt;p&gt;Many delivery workflows implicitly implement a rule like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tests pass
+
build succeeds
+
deployment artifact exists
=
release
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That works surprisingly well when systems are small and changes are simple.&lt;/p&gt;
&lt;p&gt;But as complexity grows, the equation becomes weaker.&lt;/p&gt;
&lt;p&gt;A deployment pipeline might be perfectly healthy while the release itself is questionable.&lt;/p&gt;
&lt;p&gt;For example, CI might confirm that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;lint          PASS
unit tests    PASS
integration   PASS
build         PASS
security scan PASS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yet the release could still involve a risky combination:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;large migration
+
critical service
+
low test coverage in changed area
+
no recent production rollback test
+
Friday evening deployment
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every individual check can be green while the &lt;strong&gt;combined release risk&lt;/strong&gt; is still high.&lt;/p&gt;
&lt;h3 id=&quot;tests-are-signals-not-decisions&quot;&gt;Tests are signals, not decisions&lt;/h3&gt;
&lt;p&gt;A better model is to think of tests as one category of evidence.&lt;/p&gt;
&lt;p&gt;Other evidence might include change size, affected components, dependency changes, migration risk, security findings, incident history, deployment timing, observability readiness, rollback capability, environment differences, or required approvals.&lt;/p&gt;
&lt;p&gt;The release decision then becomes something closer to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Release Decision =
    Test Evidence
  + Change Evidence
  + Operational Evidence
  + Security Evidence
  + Deployment Evidence
  + Organizational Context
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not every project needs all of these.&lt;/p&gt;
&lt;p&gt;But the important shift is conceptual:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A test result is evidence presented to a release decision. It is not the release decision itself.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;why-this-becomes-important-with-ai-generated-code&quot;&gt;Why this becomes important with AI-generated code&lt;/h3&gt;
&lt;p&gt;This distinction becomes even more important as AI coding tools become part of normal development.&lt;/p&gt;
&lt;p&gt;AI can generate code quickly.&lt;/p&gt;
&lt;p&gt;It can generate tests quickly too.&lt;/p&gt;
&lt;p&gt;That increases development speed, but it can also create an interesting failure mode:&lt;/p&gt;
&lt;p&gt;More code.&lt;/p&gt;
&lt;p&gt;More tests.&lt;/p&gt;
&lt;p&gt;More green checks.&lt;/p&gt;
&lt;p&gt;But not necessarily more understanding of the release risk.&lt;/p&gt;
&lt;p&gt;An AI-generated implementation may technically satisfy the tests while introducing architectural changes, new dependencies, unexpected operational behavior, or assumptions that were never encoded into the test suite.&lt;/p&gt;
&lt;p&gt;The answer cannot simply be “add more tests.”&lt;/p&gt;
&lt;p&gt;Tests are still essential.&lt;/p&gt;
&lt;p&gt;But teams also need a layer that asks:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What changed, what evidence do we have, and is that evidence strong enough to release?&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-missing-layer-in-many-ci-cd-systems&quot;&gt;The missing layer in many CI/CD systems&lt;/h3&gt;
&lt;p&gt;Most pipelines are very good at execution.&lt;/p&gt;
&lt;p&gt;They can run thousands of tests, build containers, scan dependencies, generate artifacts, and deploy infrastructure.&lt;/p&gt;
&lt;p&gt;But pipelines are often much weaker at explaining the final decision.&lt;/p&gt;
&lt;p&gt;Why was this release allowed?&lt;/p&gt;
&lt;p&gt;Which evidence mattered?&lt;/p&gt;
&lt;p&gt;Was anything ignored?&lt;/p&gt;
&lt;p&gt;Did the change touch a high-risk component?&lt;/p&gt;
&lt;p&gt;Was an exception approved?&lt;/p&gt;
&lt;p&gt;What would have caused the release to be blocked?&lt;/p&gt;
&lt;p&gt;In many teams, those answers exist only in someone’s head, a Slack conversation, or a manual checklist.&lt;/p&gt;
&lt;p&gt;That becomes difficult to scale.&lt;/p&gt;
&lt;p&gt;Especially when multiple repositories, services, teams, and automated agents are involved.&lt;/p&gt;
&lt;h3 id=&quot;from-pipeline-gates-to-release-evidence&quot;&gt;From pipeline gates to release evidence&lt;/h3&gt;
&lt;p&gt;A stronger release process does not replace CI.&lt;/p&gt;
&lt;p&gt;It builds on top of it.&lt;/p&gt;
&lt;p&gt;CI remains responsible for producing reliable technical signals.&lt;/p&gt;
&lt;p&gt;The release layer then evaluates those signals in context.&lt;/p&gt;
&lt;p&gt;Instead of:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;All checks passed → deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the process becomes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Collect evidence
      ↓
Evaluate release risk
      ↓
Apply policy
      ↓
Require approval when necessary
      ↓
Release / hold / reject
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That creates room for more nuanced outcomes than a simple green or red pipeline.&lt;/p&gt;
&lt;p&gt;A release might be technically valid but require human approval.&lt;/p&gt;
&lt;p&gt;Another might be blocked because rollback capability is missing.&lt;/p&gt;
&lt;p&gt;A low-risk change might be automatically approved.&lt;/p&gt;
&lt;p&gt;The important part is that the decision is &lt;strong&gt;explicit&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;green-should-mean-something&quot;&gt;Green should mean something&lt;/h3&gt;
&lt;p&gt;There is nothing wrong with a green CI pipeline.&lt;/p&gt;
&lt;p&gt;Green builds are valuable.&lt;/p&gt;
&lt;p&gt;Passing tests are valuable.&lt;/p&gt;
&lt;p&gt;Fast feedback is valuable.&lt;/p&gt;
&lt;p&gt;The problem begins when we assign them more meaning than they actually provide.&lt;/p&gt;
&lt;p&gt;A green pipeline should tell us:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The checks we defined passed.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A release decision should tell us:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Given the available evidence and our risk policy, we are willing to ship this change.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Those two statements are related.&lt;/p&gt;
&lt;p&gt;They are not identical.&lt;/p&gt;
&lt;p&gt;And as software delivery becomes increasingly automated, separating them may become one of the most important design decisions in modern CI/CD systems.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;What does “ready to release” mean in your team today? Is it simply a green pipeline, or is there another decision layer after CI?&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25305/your-tests-passed-that-still-doesnt-mean-you-should-release</guid>
<pubDate>Mon, 24 Aug 2026 15:37:01 +0000</pubDate>
</item>
<item>
<title>CI Is Green. What Could Still Go Wrong?</title>
<link>https://coderlegion.com/25304/ci-is-green-what-could-still-go-wrong</link>
<description>&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/jkbnem9ae2wkqfe61is8.png&quot; alt=&quot;Image description&quot;&gt;&lt;br&gt;
&lt;strong&gt;A green CI pipeline is a nice feeling.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The build passed.&lt;br&gt;
Tests are green.&lt;br&gt;
Linting is clean.&lt;br&gt;
Nothing obvious is on fire.&lt;/p&gt;
&lt;p&gt;For a few seconds, it feels like the release question has already been answered.&lt;/p&gt;
&lt;p&gt;It hasn't.&lt;/p&gt;
&lt;p&gt;A green pipeline tells you something useful: the checks you configured have passed under the conditions in which they were executed.&lt;/p&gt;
&lt;p&gt;That is not the same thing as saying the release is safe.&lt;/p&gt;
&lt;p&gt;And the gap between those two ideas is where a lot of production problems begin.&lt;/p&gt;
&lt;p&gt;I touched on this in my previous article about why a green CI pipeline can create a false sense of security. But there is another question that deserves more attention:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If CI is green, what can still go wrong?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Quite a lot, actually.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CI only knows what you taught it to check&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is probably the most important limitation.&lt;/p&gt;
&lt;p&gt;CI is not intelligent by default.&lt;/p&gt;
&lt;p&gt;It does not understand your product, your users, your business risk, or the weird operational history of your system.&lt;/p&gt;
&lt;p&gt;It runs instructions.&lt;/p&gt;
&lt;p&gt;If those instructions cover the right risks, great.&lt;/p&gt;
&lt;p&gt;If they don't, the pipeline can be perfectly green while the release is still dangerous.&lt;/p&gt;
&lt;p&gt;This is not really a failure of CI.&lt;/p&gt;
&lt;p&gt;It is a failure of interpretation.&lt;/p&gt;
&lt;p&gt;We often treat CI as if it has answered a bigger question than it actually has.&lt;/p&gt;
&lt;p&gt;The pipeline says:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&quot;The checks I know about passed.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Humans often hear:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&quot;This release is safe.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Those are very different statements.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The code can be correct and the release can still fail&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Imagine a change that passes every unit test and every integration test.&lt;/p&gt;
&lt;p&gt;The code itself is fine.&lt;/p&gt;
&lt;p&gt;Then it reaches production and fails because of configuration.&lt;/p&gt;
&lt;p&gt;Wrong environment variable.&lt;/p&gt;
&lt;p&gt;Missing secret.&lt;/p&gt;
&lt;p&gt;Different feature flag state.&lt;/p&gt;
&lt;p&gt;A service points to the wrong endpoint.&lt;/p&gt;
&lt;p&gt;A timeout is configured differently in production.&lt;/p&gt;
&lt;p&gt;A permission exists in staging but not in production.&lt;/p&gt;
&lt;p&gt;None of those problems necessarily mean your tests were bad.&lt;/p&gt;
&lt;p&gt;They mean the release depended on conditions outside the scope of those tests.&lt;/p&gt;
&lt;p&gt;This is one of the reasons I think we should stop treating release readiness as purely a testing problem.&lt;/p&gt;
&lt;p&gt;Testing is part of it.&lt;/p&gt;
&lt;p&gt;It is not the whole thing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Database migrations are a good example&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Database changes are one of those areas where &quot;green&quot; can be misleading.&lt;/p&gt;
&lt;p&gt;The migration itself may be syntactically correct.&lt;/p&gt;
&lt;p&gt;Tests may pass against a fresh database.&lt;/p&gt;
&lt;p&gt;Everything can look completely normal.&lt;/p&gt;
&lt;p&gt;But production has something your test environment often does not have:&lt;/p&gt;
&lt;p&gt;history.&lt;/p&gt;
&lt;p&gt;Real data.&lt;/p&gt;
&lt;p&gt;Old records.&lt;/p&gt;
&lt;p&gt;Unexpected null values.&lt;/p&gt;
&lt;p&gt;Large tables.&lt;/p&gt;
&lt;p&gt;Long-running transactions.&lt;/p&gt;
&lt;p&gt;Different indexes.&lt;/p&gt;
&lt;p&gt;Different traffic patterns.&lt;/p&gt;
&lt;p&gt;A migration that takes two seconds in CI can behave very differently against a production dataset that has existed for six years.&lt;/p&gt;
&lt;p&gt;The pipeline can be green because nothing it executed was technically wrong.&lt;/p&gt;
&lt;p&gt;The release can still be risky because the environment changed the meaning of that change.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dependencies can change the picture&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Modern software rarely runs in isolation.&lt;/p&gt;
&lt;p&gt;A release can depend on:&lt;/p&gt;
&lt;p&gt;another internal service&lt;br&gt;
a third-party API&lt;br&gt;
a package update&lt;br&gt;
a cloud service&lt;br&gt;
a message queue&lt;br&gt;
a payment provider&lt;br&gt;
a database&lt;br&gt;
an authentication system&lt;/p&gt;
&lt;p&gt;Your own repository may be completely healthy.&lt;/p&gt;
&lt;p&gt;The dependency chain around it may not be.&lt;/p&gt;
&lt;p&gt;Maybe another service has not deployed the expected API yet.&lt;/p&gt;
&lt;p&gt;Maybe a backwards-compatibility assumption is wrong.&lt;/p&gt;
&lt;p&gt;Maybe one dependency is already degraded.&lt;/p&gt;
&lt;p&gt;Maybe the release introduces a combination of versions that nobody has actually tested together.&lt;/p&gt;
&lt;p&gt;Again, CI may remain perfectly green.&lt;/p&gt;
&lt;p&gt;It tested what it could see.&lt;/p&gt;
&lt;p&gt;The release depends on more than that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Passing tests do not mean the right tests ran&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This one is uncomfortable, because it sounds obvious once you say it.&lt;/p&gt;
&lt;p&gt;A test suite can have a 100% pass rate while still telling you very little about the change you are about to release.&lt;/p&gt;
&lt;p&gt;The interesting question is not:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;How many tests passed?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Did we test the things that matter for this release?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Those are not equivalent.&lt;/p&gt;
&lt;p&gt;If somebody changes a critical authentication flow and your pipeline runs 3,000 tests mostly covering catalog functionality, the number &lt;em&gt;&quot;3,000 passed&quot;&lt;/em&gt; looks impressive.&lt;/p&gt;
&lt;p&gt;But what does it actually tell you about authentication risk?&lt;/p&gt;
&lt;p&gt;Not much.&lt;/p&gt;
&lt;p&gt;This is why I find the concept of risk coverage more useful than simply looking at test coverage or test count.&lt;/p&gt;
&lt;p&gt;What changed?&lt;/p&gt;
&lt;p&gt;What can that change affect?&lt;/p&gt;
&lt;p&gt;Which checks give us evidence about those areas?&lt;/p&gt;
&lt;p&gt;That context matters more than a big green number.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Feature flags do not magically remove risk&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Feature flags are useful.&lt;/p&gt;
&lt;p&gt;Very useful.&lt;/p&gt;
&lt;p&gt;But sometimes they become another source of false confidence.&lt;/p&gt;
&lt;p&gt;The thinking becomes:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&quot;It's behind a flag, so the release is safe.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Maybe.&lt;/p&gt;
&lt;p&gt;But the code still shipped.&lt;/p&gt;
&lt;p&gt;The flag configuration itself can be wrong.&lt;/p&gt;
&lt;p&gt;The disabled path may affect initialization.&lt;/p&gt;
&lt;p&gt;The new code may introduce dependencies before the flag is evaluated.&lt;/p&gt;
&lt;p&gt;The wrong audience may receive the feature.&lt;/p&gt;
&lt;p&gt;The rollback path may not behave the way you expect.&lt;/p&gt;
&lt;p&gt;Feature flags reduce certain kinds of risk.&lt;/p&gt;
&lt;p&gt;They do not eliminate release risk.&lt;/p&gt;
&lt;p&gt;That distinction matters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rollback is not a guarantee either&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Another sentence I hear in software teams is some version of:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&quot;If something goes wrong, we'll just roll it back.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That can be a perfectly reasonable risk strategy.&lt;/p&gt;
&lt;p&gt;But &quot;rollback exists&quot; and &quot;rollback is safe&quot; are not the same thing.&lt;/p&gt;
&lt;p&gt;What happens if the release includes a destructive migration?&lt;/p&gt;
&lt;p&gt;What happens if new data has already been written in a new format?&lt;/p&gt;
&lt;p&gt;What happens if another service starts depending on the new behavior?&lt;/p&gt;
&lt;p&gt;What happens if the release changes an external contract?&lt;/p&gt;
&lt;p&gt;Rollback sounds simple when we imagine software as a single binary moving backwards.&lt;/p&gt;
&lt;p&gt;Real systems are messier.&lt;/p&gt;
&lt;p&gt;Sometimes the release creates state that cannot be cleanly reversed.&lt;/p&gt;
&lt;p&gt;A green CI pipeline usually does not tell you whether your rollback strategy actually works.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Production traffic is different&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There is also a class of failures that only appears under real usage.&lt;/p&gt;
&lt;p&gt;Concurrency.&lt;/p&gt;
&lt;p&gt;Traffic spikes.&lt;/p&gt;
&lt;p&gt;Unusual request sequences.&lt;/p&gt;
&lt;p&gt;Large payloads.&lt;/p&gt;
&lt;p&gt;Rare account states.&lt;/p&gt;
&lt;p&gt;Slow upstream services.&lt;/p&gt;
&lt;p&gt;Network instability.&lt;/p&gt;
&lt;p&gt;Real users are extremely good at discovering combinations that test environments never produce.&lt;/p&gt;
&lt;p&gt;This is not an argument against automation.&lt;/p&gt;
&lt;p&gt;It is an argument for understanding what automation can and cannot prove.&lt;/p&gt;
&lt;p&gt;A test suite can give you evidence.&lt;/p&gt;
&lt;p&gt;It cannot give you certainty.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Observability matters before the release, not only after it&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One thing I think teams often underestimate is operational readiness.&lt;/p&gt;
&lt;p&gt;Suppose the release does contain a problem.&lt;/p&gt;
&lt;p&gt;Will you notice?&lt;/p&gt;
&lt;p&gt;How quickly?&lt;/p&gt;
&lt;p&gt;Do you have the right metrics?&lt;/p&gt;
&lt;p&gt;Do you have logs around the changed area?&lt;/p&gt;
&lt;p&gt;Will an alert actually fire?&lt;/p&gt;
&lt;p&gt;Can you distinguish expected noise from a real regression?&lt;/p&gt;
&lt;p&gt;A release with some uncertainty but excellent observability can sometimes be safer than a release with more passing tests but poor visibility.&lt;/p&gt;
&lt;p&gt;Because release safety is not only about preventing every possible failure.&lt;/p&gt;
&lt;p&gt;It is also about limiting the damage when a failure happens.&lt;/p&gt;
&lt;p&gt;That changes the release decision.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Human context still matters&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Not every release risk is technical.&lt;/p&gt;
&lt;p&gt;Sometimes the biggest risk is simply that nobody has the full picture.&lt;/p&gt;
&lt;p&gt;QA knows about one unresolved issue.&lt;/p&gt;
&lt;p&gt;A developer knows the fix was rushed.&lt;/p&gt;
&lt;p&gt;Operations knows a dependent system has been unstable all afternoon.&lt;/p&gt;
&lt;p&gt;Product knows a feature absolutely must not reach one customer segment yet.&lt;/p&gt;
&lt;p&gt;Security raised a concern that is technically &quot;non-blocking.&quot;&lt;/p&gt;
&lt;p&gt;Each piece of information exists.&lt;/p&gt;
&lt;p&gt;But it exists in different places, owned by different people.&lt;/p&gt;
&lt;p&gt;Then the pipeline turns green.&lt;/p&gt;
&lt;p&gt;And everyone assumes somebody else must have checked the rest.&lt;/p&gt;
&lt;p&gt;This is where release processes become dangerous.&lt;/p&gt;
&lt;p&gt;Not because there was no information.&lt;/p&gt;
&lt;p&gt;Because the information never became part of the decision.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So what does green CI actually mean?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I still want green CI.&lt;/p&gt;
&lt;p&gt;Obviously.&lt;/p&gt;
&lt;p&gt;A red pipeline is useful information too, but I would much rather start from green.&lt;/p&gt;
&lt;p&gt;The problem is not the green status.&lt;/p&gt;
&lt;p&gt;The problem is giving that status more authority than it deserves.&lt;/p&gt;
&lt;p&gt;For me, a green pipeline should mean something closer to:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;The automated checks we currently require did not detect a blocking problem.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That is a strong signal.&lt;/p&gt;
&lt;p&gt;It is not yet a release decision.&lt;/p&gt;
&lt;p&gt;A real release decision needs more context.&lt;/p&gt;
&lt;p&gt;Depending on the system, I would want to know things like:&lt;/p&gt;
&lt;p&gt;What changed?&lt;br&gt;
How risky is that area?&lt;br&gt;
Which relevant tests actually ran?&lt;br&gt;
Are there unresolved defects?&lt;br&gt;
Are dependencies healthy and compatible?&lt;br&gt;
Are migrations involved?&lt;br&gt;
Are security checks clean?&lt;br&gt;
Do we have sufficient observability?&lt;br&gt;
Can we roll back safely?&lt;br&gt;
Is there any known operational or business risk?&lt;/p&gt;
&lt;p&gt;Only then does the question become interesting:&lt;/p&gt;
&lt;p&gt;Do we have enough evidence to release?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maybe the problem is the way we frame CI&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We built CI to automate verification.&lt;/p&gt;
&lt;p&gt;Over time, many teams started using it as a proxy for release readiness.&lt;/p&gt;
&lt;p&gt;That shortcut works surprisingly often.&lt;/p&gt;
&lt;p&gt;Which is exactly why it is dangerous.&lt;/p&gt;
&lt;p&gt;Most releases with a green pipeline are probably fine.&lt;/p&gt;
&lt;p&gt;Then one isn't.&lt;/p&gt;
&lt;p&gt;And when you investigate what happened, the answer is often not:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&quot;CI failed.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It is:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&quot;CI never knew that this was something it needed to evaluate.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That is the gap I keep coming back to.&lt;/p&gt;
&lt;p&gt;The future of release engineering, in my opinion, is not just more tests and faster pipelines.&lt;/p&gt;
&lt;p&gt;It is better decision-making.&lt;/p&gt;
&lt;p&gt;More context.&lt;/p&gt;
&lt;p&gt;Better evidence.&lt;/p&gt;
&lt;p&gt;A clearer understanding of what we know before we put a change in front of users.&lt;/p&gt;
&lt;p&gt;Green CI should absolutely make us more confident.&lt;/p&gt;
&lt;p&gt;It just shouldn't make us stop asking questions.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25304/ci-is-green-what-could-still-go-wrong</guid>
<pubDate>Mon, 24 Aug 2026 15:30:17 +0000</pubDate>
</item>
<item>
<title>Continuous Delivery Office Hours Ep.5: Delivering database changes</title>
<link>https://coderlegion.com/25292/continuous-delivery-office-hours-ep-5-delivering-database-changes</link>
<description>&lt;p&gt;In the previous episode, we talked about &lt;a rel=&quot;nofollow&quot; href=&quot;/blog/continuous-delivery-office-hours-e4&quot;&gt;different service design approaches&lt;/a&gt;. This time, we dive deeper into database changes.&lt;/p&gt;
&lt;p&gt;Even for teams that can effortlessly deploy their application code, database changes can be more stressful. Changing a schema is a high-stakes operation, and there are many ways to do it badly.&lt;/p&gt;
&lt;p&gt;Read on to find out why database deployments are different from application deployments, and what techniques you can use to make them worry-free.&lt;/p&gt;
&lt;h3 id=&quot;watch-the-episode&quot;&gt;Watch the episode&lt;/h3&gt;
&lt;p&gt;You can watch the episode below, or read on to find some of the key discussion points.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=_rHyP_RZbQc&quot;&gt;Watch Continuous Delivery Office Hours Ep.5&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;why-are-databases-different&quot;&gt;Why are databases different&lt;/h3&gt;
&lt;p&gt;When you change your application's code and discover an issue, it's trivial to revert to the previous version. In rare cases where a change affects your data, there may be cleanup to do.&lt;/p&gt;
&lt;p&gt;You can use timestamps and a one-off process to fix those rare data-mess-up instances, but they hint at something fundamentally different about database updates. They have different levels and types of risk associated with them.&lt;/p&gt;
&lt;p&gt;If you think backups will save you from a bad database deployment, you haven't yet tried it. Sure, they prevent total data loss, but between your backup and your fix, the data moved. Often by a lot.&lt;/p&gt;
&lt;p&gt;There are techniques to apply transactions from the backup up to the point of failure, but if your change caused an issue you needed to roll back, you probably don't want to apply those transactions automatically. Welcome to the &quot;data remediation project&quot;.&lt;/p&gt;
&lt;p&gt;If you added a new column or table as part of your database change and you need to roll back, you have to decide what to do with any data in those tables. Do you forget it, or do you need to keep hold of it and reapply it later when you make a new attempt to extend the schema?&lt;/p&gt;
&lt;p&gt;Modern software teams prefer fix-forward for application issues, which are reasonably easy to roll back. Databases take rollbacks to another level.&lt;/p&gt;
&lt;h3 id=&quot;crucial-modernization-steps&quot;&gt;Crucial modernization steps&lt;/h3&gt;
&lt;p&gt;Imagine you met a friend for lunch and they told you they store they application's source code on a network share, rather than in version control. You'd think it was a joke, and when you realize it's not, you'd form a strong opinion about the kind of sloppy outfit they must be running.&lt;/p&gt;
&lt;p&gt;When we meet for lunch, what are you going to tell me about your database schema and static data? Please tell me it's all in version control, not on a network share.&lt;/p&gt;
&lt;p&gt;You should make all database changes by updating the files in version control and deploying them like you would your application code. You progress the change through environments to ensure it works, and you avoid embarrassment caused by the application failing because someone forgot to add the new column in production.&lt;/p&gt;
&lt;p&gt;There are further choices to make, which we'll cover next, but failing to version-control your database is unforgivable.&lt;/p&gt;
&lt;h3 id=&quot;state-based-vs-migration-based-approaches&quot;&gt;State-based vs migration-based approaches&lt;/h3&gt;
&lt;p&gt;On to the first choice for your database project. Do you make state-based or migration-based updates?&lt;/p&gt;
&lt;p&gt;State-based schemas describe the desired state of the database. It will list each table with its columns, indexes, and relationships. You use a model-based tool to deploy the database, which compares your current state with the desired state and applies the changes for you.&lt;/p&gt;
&lt;p&gt;Some state-based tools convert the differences into standard database scripts, like &lt;code&gt;ALTER TABLE...&lt;/code&gt; scripts. Others perform migrations by creating a new table with the changes and moving the data into it. This is important if you're using a technology like replication, which prevents the model migration mechanism from working.&lt;/p&gt;
&lt;p&gt;The alternative to state-based database updates is migration-based updates, where you write your own &lt;code&gt;ALTER TABLE&lt;/code&gt; scripts. You keep all your scripts in version control and use a tool that applies them in order and tracks when each was applied, preventing the same script from being applied twice to the same database.&lt;/p&gt;
&lt;p&gt;The main difference between the two is procedural. You can code-review the migration scripts on demand, but you'll need to review state-based migration scripts after your tools generate the implementation plan, which can make the review task more of a large batch.&lt;/p&gt;
&lt;h3 id=&quot;tooling-and-automation&quot;&gt;Tooling and automation&lt;/h3&gt;
&lt;p&gt;Whichever approach you use, tooling helps it work. Many of our customers use Redgate tools as part of their deployment process to manage database schema changes, and there's value in leaning on a tool written by folks who care deeply about the problem and how to solve it.&lt;/p&gt;
&lt;p&gt;Crucially, you shouldn't make any database changes outside of tool-based automation.&lt;/p&gt;
&lt;h3 id=&quot;test-data-management&quot;&gt;Test data management&lt;/h3&gt;
&lt;p&gt;Once you've automated your database schema and static data, it's worth considering your test data. When automated or manual acceptance tests fail, it's usually because someone unwittingly messed up the data. Prior test runs often leave data in an inconsistent state, especially if a test failure halts the run.&lt;/p&gt;
&lt;p&gt;You can resolve this issue by automating your test data setup. Not only does this make it easy to reset the data during your build and test cycle, but it also lets you provide a self-service runbook for the test team to reset the data in their test environment whenever they need to.&lt;/p&gt;
&lt;p&gt;There's an up-front investment in this, but I can promise that it takes fewer hours than fixing your test data a few times.&lt;/p&gt;
&lt;h3 id=&quot;database-refactoring-patterns&quot;&gt;Database refactoring patterns&lt;/h3&gt;
&lt;p&gt;The final thought to ponder concerns the steps you take in changing your database schema. When you're in the habit of deploying your database and application in the same release process, you start to depend on this change coordination.&lt;/p&gt;
&lt;p&gt;You delete a column from the database and immediately deploy the application, with all references to the deleted column removed. It looks like it works smoothly, but it's a trap.&lt;/p&gt;
&lt;p&gt;Imagine you had a critical bug in the application and had to redeploy the previous version. Now you can't, because the previous version will try to read from a column that doesn't exist. You no longer have a quick, easy back out plan, since you have to re-add the column, and you'll also need data to put in it.&lt;/p&gt;
&lt;p&gt;This approach also prevents you from making seamless deployments, as even if you progressively roll out the application version, the old version will error out due to the database change. You may deploy in the opposite order, application first, then database. You'll discover the same problem with new columns that the latest version expects to find in the database.&lt;/p&gt;
&lt;p&gt;You need to decouple database and application deployments, and there's a whole book on the topic, called Refactoring Databases (Ambler, Sadalage). You can start by following the expand/contract pattern, which splits updates into steps. The principle is that you don't delete a column until the production application has no reads or writes. You add a column and don't reference it in your code until you deploy to production.&lt;/p&gt;
&lt;p&gt;This means you can run the current version and the new version of the software against the same database, which means you can progressively roll out the new version and redeploy the prior version without touching the database.&lt;/p&gt;
&lt;h3 id=&quot;databases-are-only-as-hard-as-you-make-them&quot;&gt;Databases are only as hard as you make them&lt;/h3&gt;
&lt;p&gt;The database is high-risk, which is why updating it can be scary. I hope you've found this post full of practical advice for making database deployments robust and stress-free.&lt;/p&gt;
&lt;p&gt;Database deployments, like application deployments, should be a happy time. You should be celebrating the new features and enhancements you've delivered to your users, not biting your nails and worrying that something's about to go horribly wrong.&lt;/p&gt;
&lt;p&gt;Happy deployments!&lt;/p&gt;
&lt;p&gt;Continuous Delivery Office Hours is a series of conversations about software delivery, with Tony Kelly, Bob Walker, and Steve Fenton.&lt;/p&gt;
&lt;p&gt;You can find more episodes on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/playlist?list=PLAGskdGvlaw3CrxkUOAMmiy928lr5D4oh&quot;&gt;YouTube&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://podcasts.apple.com/us/podcast/continuous-delivery-office-hours/id1872101651&quot;&gt;Apple Podcasts&lt;/a&gt;, and &lt;a rel=&quot;nofollow&quot; href=&quot;https://pca.st/hwjaox59&quot;&gt;Pocket Casts&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25292/continuous-delivery-office-hours-ep-5-delivering-database-changes</guid>
<pubDate>Mon, 24 Aug 2026 12:57:43 +0000</pubDate>
</item>
<item>
<title>Your CI Pipeline Is the Most Privileged Machine You Own. You Have Never Scanned It.</title>
<link>https://coderlegion.com/25110/your-ci-pipeline-is-the-most-privileged-machine-you-own-you-have-never-scanned-it</link>
<description>&lt;p&gt;You scan your application code. You scan your dependencies. You scan your containers.&lt;/p&gt;
&lt;p&gt;You have never scanned the thing that has access to all of them.&lt;/p&gt;
&lt;p&gt;Your CI pipeline holds your deploy keys. Your registry credentials. Your cloud tokens. Your signing secrets. It runs on every push, it runs third-party code you did not write, and it runs with more privilege than any developer on your team.&lt;/p&gt;
&lt;p&gt;It is the most privileged machine you own. And for most projects, it is completely unreviewed.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-attackers-target-the-pipeline&quot;&gt;Why Attackers Target The Pipeline&lt;/h3&gt;
&lt;p&gt;An attacker who compromises your laptop gets your laptop.&lt;/p&gt;
&lt;p&gt;An attacker who compromises your pipeline gets production.&lt;/p&gt;
&lt;p&gt;They do not need to find a vulnerability in your code. They do not need to phish a developer. They need one workflow file that trusts something it should not. The pipeline then does the work for them, with credentials you handed it, inside a network you allowed.&lt;/p&gt;
&lt;p&gt;This is not theoretical. Supply chain attacks moved to CI years ago because that is where the credentials live. The Codecov bash uploader compromise in 2021 exposed secrets from thousands of CI environments. In March 2025, the widely used &lt;code&gt;tj-actions/changed-files&lt;/code&gt; action was compromised and began leaking secrets into build logs of every repository that referenced it by tag.&lt;/p&gt;
&lt;p&gt;Both attacks worked the same way. Trusted third-party code, running with pipeline privileges, on somebody else's push.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-actions-pinned-to-tags-instead-of-commits&quot;&gt;1. Actions Pinned To Tags Instead Of Commits&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The mistake:&lt;/strong&gt; Your workflow says &lt;code&gt;uses: some-org/some-action@v3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A tag is a pointer. Whoever controls the repository controls where it points. If that account is compromised, &lt;code&gt;v3&lt;/code&gt; silently becomes attacker code on your next run. You changed nothing. You approved nothing. You pulled it anyway.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Pin every third-party action to a full commit SHA.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;# Do not do this
uses: some-org/some-action@v3

# Do this
uses: some-org/some-action@a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A SHA cannot be moved. Add a comment with the version so future you knows what it was.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to catch it:&lt;/strong&gt; &lt;code&gt;grep -rn &quot;uses:.*@v&quot; .github/workflows/&lt;/code&gt; lists every unpinned action you have. Do it now. The result is usually worse than expected.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-secrets-available-to-every-job&quot;&gt;2. Secrets Available To Every Job&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The mistake:&lt;/strong&gt; Your secrets are repository-wide. Every workflow can read them. That includes the lint job. That includes the job that runs a third-party formatter on a fork's pull request.&lt;/p&gt;
&lt;p&gt;A secret is exposed to everything that can read it, not just to the step you were thinking about when you added it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Scope secrets to environments, not to the repository, and require approval for the environments that hold production credentials. A test job does not need a deploy key. Give it nothing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to catch it:&lt;/strong&gt; List your repository secrets. For each one, name the single job that needs it. Anything you cannot assign to a specific job is over-shared.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-workflows-that-run-on-untrusted-pull-requests&quot;&gt;3. Workflows That Run On Untrusted Pull Requests&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The mistake:&lt;/strong&gt; You use &lt;code&gt;pull_request_target&lt;/code&gt; because you needed a token that &lt;code&gt;pull_request&lt;/code&gt; does not have, and then you check out the pull request head.&lt;/p&gt;
&lt;p&gt;That combination runs attacker-controlled code with your secrets in scope. It is the single most reliable way to leak credentials from a public repository.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Use &lt;code&gt;pull_request&lt;/code&gt; for anything that touches untrusted code. If you genuinely need privileged work on a fork's contribution, split it: an unprivileged job builds and uploads an artifact, a separate privileged job consumes the artifact without ever checking out the fork's code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to catch it:&lt;/strong&gt; &lt;code&gt;grep -rn &quot;pull_request_target&quot; .github/workflows/&lt;/code&gt;. Every hit needs a written justification. If nobody remembers why it is there, it should not be there.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-default-write-permissions-on-the-job-token&quot;&gt;4. Default Write Permissions On The Job Token&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The mistake:&lt;/strong&gt; You never set &lt;code&gt;permissions:&lt;/code&gt;, so the automatic token gets whatever the repository default is. On older repositories, that default is write. Any compromised step in that job can push commits, edit releases, or open pull requests as your CI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Default to nothing and grant upward per job.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;permissions:
  contents: read

jobs:
  build:
    permissions:
      contents: read
  release:
    permissions:
      contents: write
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;How to catch it:&lt;/strong&gt; Any workflow file with no &lt;code&gt;permissions:&lt;/code&gt; block is running on defaults. Search for the files that lack it, not the ones that have it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-secrets-printed-into-logs&quot;&gt;5. Secrets Printed Into Logs&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The mistake:&lt;/strong&gt; Someone added &lt;code&gt;set -x&lt;/code&gt; while debugging, or a step echoes a config object, or a failing command dumps its environment. Masking catches exact string matches. It does not catch a secret that has been base64 encoded, JSON wrapped, or split across a variable expansion.&lt;/p&gt;
&lt;p&gt;Build logs on public repositories are public. Artifacts are downloadable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; Never echo configuration. Remove debug tracing before merge. Treat log output as published, because it is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How to catch it:&lt;/strong&gt; Run a secret scanner across your own build logs and artifacts, not just your source tree. Gitleaks and TruffleHog work on any text. Most people never point them at the one place secrets actually get printed.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-ten-minute-audit&quot;&gt;The Ten Minute Audit&lt;/h3&gt;
&lt;p&gt;You do not need a new tool for this. Open a terminal in your repository:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Unpinned third-party actions
grep -rn &quot;uses:.*@v&quot; .github/workflows/

# Workflows that run untrusted code with privileges
grep -rn &quot;pull_request_target&quot; .github/workflows/

# Workflows with no explicit permissions block
grep -rLn &quot;permissions:&quot; .github/workflows/*.yml

# Debug tracing left behind
grep -rn &quot;set -x&quot; .github/workflows/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four commands. Ten minutes. Fix the SHAs first, then the permissions blocks. That covers most of the realistic attack surface.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-stays-broken&quot;&gt;Why This Stays Broken&lt;/h3&gt;
&lt;p&gt;Pipeline security falls between two teams. Application security scanners read your source code, not your workflow files. Infrastructure scanners read your Terraform, not your build steps. The pipeline sits in the gap, and the gap is where the credentials are.&lt;/p&gt;
&lt;p&gt;The tools that do read workflow files exist. Checkov covers CI configuration, Semgrep has rules for GitHub Actions, and actionlint catches the structural problems. None of them is the default in a new repository, which is exactly why this keeps working for attackers.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-bottom-line&quot;&gt;The Bottom Line&lt;/h3&gt;
&lt;p&gt;Your pipeline has more privilege than you do. Scan it like it does.&lt;/p&gt;
&lt;p&gt;Pin your actions to SHAs. Scope your secrets. Never run untrusted code with credentials in reach. Then go look at what your build logs have been printing this whole time.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;discussion-question&quot;&gt;Discussion Question&lt;/h3&gt;
&lt;p&gt;Go run &lt;code&gt;grep -rn &quot;uses:.*@v&quot; .github/workflows/&lt;/code&gt; right now. How many unpinned actions did you find? I want to know if anyone is actually at zero.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25110/your-ci-pipeline-is-the-most-privileged-machine-you-own-you-have-never-scanned-it</guid>
<pubDate>Fri, 21 Aug 2026 20:17:57 +0000</pubDate>
</item>
<item>
<title>How do I know when my website is ready for deployment?</title>
<link>https://coderlegion.com/25061/how-do-i-know-when-my-website-is-ready-for-deployment</link>
<description>&lt;p&gt;Forms passing in staging does not mean the site is fast enough to publish. A short checklist for speed budgets on priority URLs, CI checks, production previews, and what to measure after deploy.&lt;/p&gt;
&lt;p&gt;On Thursday the team closed the last functional ticket. Forms submitted, redirects resolved, analytics fired in staging. On Friday someone ran PageSpeed Insights on the production preview URL and mobile INP on checkout was amber while the homepage looked fine.&lt;/p&gt;
&lt;p&gt;That sequence is common. Functional QA answers whether the site works. Speed readiness answers whether the build you are about to publish keeps Core Web Vitals inside the budgets you set for the URLs that handle signups, sales, or support traffic. Below is the checklist we use when someone asks how to know a website is ready for deployment, with speed checks listed separately from forms and content review.&lt;/p&gt;
&lt;h3 id=&quot;why-does-passing-functional-qa-not-mean-the-site-is-ready-to-deploy-&quot;&gt;Why does passing functional QA not mean the site is ready to deploy?&lt;/h3&gt;
&lt;p&gt;Functional QA checks behaviour: links resolve, auth flows complete, carts total correctly, CMS entries render, consent banners appear. Speed readiness checks load time and stability on representative page templates under realistic throttling, usually mobile first.&lt;/p&gt;
&lt;p&gt;A site can pass the first checklist and still ship a regression. Typical causes include a hero image without dimensions on the homepage template only, a new marketing tag that blocks the main thread on checkout, a font swap that shifts layout on product pages, or a CI job that only audited the marketing homepage while app routes changed in the same release.&lt;/p&gt;
&lt;p&gt;Treat them as two separate approvals. Functional QA covers whether things work. Speed readiness covers LCP, INP, and CLS (plus supporting lab metrics such as TBT where INP is noisy) on the URL list you care about after deploy. Both need saved evidence, not a verbal &quot;looks fast.&quot;&lt;/p&gt;
&lt;h3 id=&quot;what-pre-deploy-performance-tests-should-you-run-on-priority-urls-&quot;&gt;What pre-deploy performance tests should you run on priority URLs?&lt;/h3&gt;
&lt;p&gt;Pre-deploy performance testing should cover more than one URL and more than one device type. At minimum, run lab audits on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Homepage (discovery and first load)&lt;/li&gt;
&lt;li&gt;One high-traffic landing or campaign page&lt;/li&gt;
&lt;li&gt;One conversion route (pricing, signup, contact, or checkout step)&lt;/li&gt;
&lt;li&gt;One content template (blog post, docs page, or help article) if it is built differently&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Run each URL on mobile and desktop strategies in PageSpeed Insights or Lighthouse. Record LCP, INP (or TBT on older tooling), CLS, and the Performance score. Compare against your budgets, not only Google's &quot;Good&quot; thresholds if the client SOW is tighter.&lt;/p&gt;
&lt;p&gt;Use the environment you will actually ship. A Lighthouse run on localhost misses CDN behaviour, production analytics, and certificate or HSTS differences. A preview URL on the production host (or the live domain before you switch DNS) is the audit you should trust for the publish decision. Staging on a different subdomain is useful for functional QA; it is weaker evidence for speed unless it uses the same tags and caching as production.&lt;/p&gt;
&lt;p&gt;Save PDF or JSON exports with timestamps. You will need a baseline to compare against the first production week. &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/core-web-vitals-monitoring-checklist-for-agencies?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=hashnode-website-ready-for-deployment-checklist&quot;&gt;Core Web Vitals Monitoring Checklist for Agencies&lt;/a&gt; includes onboarding baselines and budget rows you can reuse for a single-site launch.&lt;/p&gt;
&lt;h3 id=&quot;what-does-a-ci-performance-check-prove-before-deployment-&quot;&gt;What does a CI performance check prove before deployment?&lt;/h3&gt;
&lt;p&gt;If your pipeline already runs Lighthouse CI or similar, treat it as a check on the URLs configured in &lt;code&gt;lighthouserc&lt;/code&gt; (or equivalent). A green CI build means: this build did not cross the assertions you defined on those routes in a controlled lab run.&lt;/p&gt;
&lt;p&gt;It does not prove:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every template in the release was in the URL list&lt;/li&gt;
&lt;li&gt;Production third-party tags match preview&lt;/li&gt;
&lt;li&gt;Field data in Chrome User Experience Report will move within days (CrUX lags)&lt;/li&gt;
&lt;li&gt;Mobile traffic mix on the live domain will match the lab profile&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CI belongs in the checklist as &quot;pipeline green on configured URLs.&quot; It complements pre-deploy manual runs; it does not replace scheduled monitoring after the site is live. &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/how-to-set-up-performance-budgets-in-ci-cd-pipelines?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=hashnode-website-ready-for-deployment-checklist&quot;&gt;How to Set Up Performance Budgets in CI/CD Pipelines&lt;/a&gt; walks through assertions and preview hosts if you are adding a check for the first time. A separate note on which checks belong in CI versus ongoing monitoring covers day-to-day ownership for teams that already run pipelines every day.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-baseline-before-ship-and-compare-after-deploy-&quot;&gt;How do you baseline before ship and compare after deploy?&lt;/h3&gt;
&lt;p&gt;Before deploy, store a row per URL and strategy: date, environment (preview or production host), LCP, INP, CLS, Performance score, and notes on the LCP element or layout shift source. After deploy, rerun the same URLs at 24 hours and 72 hours on the live domain.&lt;/p&gt;
&lt;p&gt;Compare like for like. A mobile lab run before deploy should be compared to mobile lab after deploy, not to desktop field data in Search Console. If scores move, check whether DNS, caching, tag managers, or A/B snippets changed between runs.&lt;/p&gt;
&lt;p&gt;If CrUX field data already exists for the domain, record the prior 28-day window but do not expect launch-week fixes to appear immediately. Use lab baselines for the publish decision; use field trends for the first monthly review.&lt;/p&gt;
&lt;h3 id=&quot;do-third-party-and-consent-scripts-still-block-render-on-the-production-url-&quot;&gt;Do third-party and consent scripts still block render on the production URL?&lt;/h3&gt;
&lt;p&gt;Consent managers, chat widgets, A/B platforms, and analytics often load only on the production domain. A fast staging build can still ship heavy tags on the URL customers hit.&lt;/p&gt;
&lt;p&gt;On the production preview (or live domain in quiet hours), open DevTools Performance or Lighthouse and confirm:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Consent banner does not delay LCP element paint beyond budget&lt;/li&gt;
&lt;li&gt;Chat or support widgets defer until after first interaction or idle&lt;/li&gt;
&lt;li&gt;Tag manager containers match the approved production list, not a staging container&lt;/li&gt;
&lt;li&gt;New pixels added for launch week are documented with an owner&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If marketing added &quot;just one script&quot; after the last green Lighthouse run, rerun audits before DNS. That single script is a frequent reason checkout passes functional QA and fails INP on mobile.&lt;/p&gt;
&lt;h3 id=&quot;when-is-one-green-lighthouse-run-not-enough-for-deployment-&quot;&gt;When is one green Lighthouse run not enough for deployment?&lt;/h3&gt;
&lt;p&gt;One green Lighthouse score is not enough when any of these are true:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Situation &lt;/th&gt;&lt;th&gt;Why one run fails&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Only the homepage was tested &lt;/td&gt;&lt;td&gt;Regressions hide on checkout, account, or search results&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Only desktop was tested &lt;/td&gt;&lt;td&gt;Google uses mobile signals for ranking and most retail traffic is mobile&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Run happened before final content or tags &lt;/td&gt;&lt;td&gt;Hero media, fonts, or GTM changes after the audit&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Scores sit just inside budget &lt;/td&gt;&lt;td&gt;Lighthouse variance can flip a borderline INP or LCP on the next run&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CI and manual runs disagree &lt;/td&gt;&lt;td&gt;Investigate URL list, throttling, or auth differences before ship&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;No baseline file exists &lt;/td&gt;&lt;td&gt;You cannot prove regressions post-launch without a saved before row&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Run at least three consecutive lab runs on critical URLs and compare medians when scores are borderline. For high-stakes launches, keep a short rollback plan and the previous build available.&lt;/p&gt;
&lt;h3 id=&quot;website-ready-for-deployment-checklist-speed-&quot;&gt;Website ready for deployment checklist (speed)&lt;/h3&gt;
&lt;p&gt;Use this table beside your functional QA list. Mark each row pass only with evidence (report link, screenshot, or CI job URL).&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Check &lt;/th&gt;&lt;th&gt;Pass criteria &lt;/th&gt;&lt;th&gt;Evidence to attach&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Priority URLs named &lt;/td&gt;&lt;td&gt;Homepage, conversion route, and one secondary template listed &lt;/td&gt;&lt;td&gt;URL list in ticket or runbook&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Mobile lab budgets &lt;/td&gt;&lt;td&gt;LCP, INP, CLS within agreed limits on each priority URL &lt;/td&gt;&lt;td&gt;PageSpeed Insights or Lighthouse export&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Desktop lab spot-check &lt;/td&gt;&lt;td&gt;Same URLs on desktop; no desktop-only surprises &lt;/td&gt;&lt;td&gt;Second export per URL&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Production or preview host &lt;/td&gt;&lt;td&gt;Audits run on the host that will serve customers, not only localhost &lt;/td&gt;&lt;td&gt;URL bar shows production or approved preview&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CI check (if used) &lt;/td&gt;&lt;td&gt;Pipeline green on configured routes for this commit &lt;/td&gt;&lt;td&gt;CI job link&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Third-party / consent review &lt;/td&gt;&lt;td&gt;Production tags loaded; no undeclared script after last audit &lt;/td&gt;&lt;td&gt;Tag assistant screenshot or container diff&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Baseline stored &lt;/td&gt;&lt;td&gt;Before-deploy metrics saved per URL and device type &lt;/td&gt;&lt;td&gt;Sheet or monitoring tool snapshot&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Post-deploy plan &lt;/td&gt;&lt;td&gt;Reruns scheduled at 24h and 72h; alerts configured &lt;/td&gt;&lt;td&gt;Calendar invite or monitor schedule&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rollback named &lt;/td&gt;&lt;td&gt;Person and steps documented if INP or LCP breach after publish &lt;/td&gt;&lt;td&gt;Runbook link&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Functional items (forms, SEO meta, redirects, SSL, accessibility spot checks) still belong in your full launch QA. This table is the speed section for &quot;ready for deployment&quot; when people treat green tickets as proof the site is fast.&lt;/p&gt;
&lt;h3 id=&quot;faq&quot;&gt;FAQ&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Is staging enough for a speed check before publish?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Staging is enough for functional QA when behaviour matches production. For speed, prefer the production host or a preview URL that uses production CDN, certificates, and tag containers. If staging omits analytics or consent, run a final audit where those scripts load.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Does a 90+ Lighthouse Performance score mean we are ready?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A high score on one URL is encouraging, not sufficient. Confirm priority routes, mobile tests, and third-party scripts on the live host. Scores also weight metrics differently from field Core Web Vitals; treat Lighthouse as a lab check, not a CrUX guarantee.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Should deployment wait for CrUX to turn green?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No for initial launch. CrUX needs traffic and rolls on a 28-day window. Publish when lab baselines and CI checks pass on the production URL, then watch field data and scheduled monitoring in the first weeks.&lt;/p&gt;
&lt;h3 id=&quot;what-to-do-the-week-before-you-switch-dns&quot;&gt;What to do the week before you switch DNS&lt;/h3&gt;
&lt;p&gt;Name your priority URLs in writing. Run mobile and desktop lab audits on the production preview and save baselines. Confirm CI assertions cover at least one non-home template if the release touched app routes. Rerun audits after any late tag or hero media change. Schedule post-deploy reruns before you announce the launch.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/check?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=hashnode-website-ready-for-deployment-checklist&quot;&gt;Run a free domain PageSpeed check&lt;/a&gt; on the URLs in your publish list, or &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/sign-up?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=hashnode-website-ready-for-deployment-checklist&quot;&gt;start a trial&lt;/a&gt; to keep the same routes on a schedule after deploy.&lt;/p&gt;
&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://googlechrome.github.io/lighthouse-ci/docs/getting-started.html&quot;&gt;Getting started with Lighthouse CI&lt;/a&gt; (Chrome Developers)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://web.dev/vitals/&quot;&gt;Web Vitals&lt;/a&gt; (web.dev)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/core-web-vitals-monitoring-checklist-for-agencies&quot;&gt;Core Web Vitals Monitoring Checklist for Agencies&lt;/a&gt; (Apogee Watcher)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/how-to-set-up-performance-budgets-in-ci-cd-pipelines&quot;&gt;How to Set Up Performance Budgets in CI/CD Pipelines&lt;/a&gt; (Apogee Watcher)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.hashnode.dev/website-ready-for-deployment-checklist&quot;&gt;Hashnode&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25061/how-do-i-know-when-my-website-is-ready-for-deployment</guid>
<pubDate>Fri, 21 Aug 2026 06:29:41 +0000</pubDate>
</item>
<item>
<title># The Revolution in Protocol Security: How It Actually Gets Used</title>
<link>https://coderlegion.com/24974/the-revolution-in-protocol-security-how-it-actually-gets-used</link>
<description>&lt;p&gt;&lt;strong&gt;A follow-up. The previous article described what we are building. This one describes how it works in practice - and why the shift from academic tool to industrial platform matters.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;In a previous article, I described the architecture of a protocol analyzer we are building: automatic repair, sixteen attacker models, GNN-guided search, modular crate design. That was the &quot;what.&quot; This article is the &quot;how&quot; - how this tool gets used, what it changes in practice, and why we believe it represents a shift from laboratory instrument to industrial platform.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-with-existing-tools-is-not-only-capability---it-is-also-access&quot;&gt;The problem with existing tools is not only capability - it is also access&lt;/h3&gt;
&lt;p&gt;ProVerif and Tamarin are remarkable achievements in formal methods. They are also laboratory instruments by design. You install them on a research machine, write a model in their DSL, run analysis in a terminal, read the output. This workflow is fine for a PhD student. It does not work for a security engineer at a company that ships TLS implementations.&lt;/p&gt;
&lt;p&gt;The gap concerns where these tools live in the development lifecycle, not what they can prove. Existing tools sit outside CI pipelines, outside IDEs, outside deployment workflows. They are consulted - if at all - after a protocol is designed, before it ships. Rarely during development, rarely after deployment.&lt;/p&gt;
&lt;p&gt;We are building something that lives inside the lifecycle.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;ci-cd-integration-verify-on-every-commit&quot;&gt;CI/CD integration: verify on every commit&lt;/h3&gt;
&lt;p&gt;The analyzer ships as a GitHub Action. Every time a protocol description changes - a new message flow, a modified key exchange, an added authentication step - the analyzer runs automatically. It checks whether the change introduces a vulnerability. If it does, the action fails with a concrete attack trace and a proposed fix.&lt;/p&gt;
&lt;p&gt;This is not a theoretical capability. The action is designed to run in minutes on standard CI hardware, not hours on a research cluster. The performance targets we described in the previous article - 10x to 50x speedup, sub-ten-minute analysis for real-world protocols - exist to make this practical. A tool that takes six hours to analyze a protocol cannot run in CI. One that takes six minutes can.&lt;/p&gt;
&lt;p&gt;Beyond GitHub Actions, the analyzer provides a CLI that integrates with any CI system - GitLab CI, Jenkins, Buildkite. The output is machine-readable: SARIF for security scanners, JSON for custom pipelines, JUnit XML for test reporting. Protocol verification becomes another test suite in the pipeline.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;ide-integration-find-vulnerabilities-while-you-write&quot;&gt;IDE integration: find vulnerabilities while you write&lt;/h3&gt;
&lt;p&gt;The analyzer ships a Language Server Protocol (LSP) implementation. In a VS Code editor, this means: syntax highlighting for the protocol DSL, inline error reporting as you type, hover documentation for security properties, and one-click analysis from the editor.&lt;/p&gt;
&lt;p&gt;The experience is analogous to writing Rust with rust-analyzer: the tool understands what you are writing and gives feedback in real time. When you define a key exchange step, the analyzer checks whether the key material is properly authenticated. When you add a new message, it checks whether the freshness guarantees hold. The vulnerability is flagged before you commit the code - not after.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;real-time-analysis-the-daemon-and-the-sniffer&quot;&gt;Real-time analysis: the daemon and the sniffer&lt;/h3&gt;
&lt;p&gt;This is where the tool stops being a verifier and becomes a platform.&lt;/p&gt;
&lt;p&gt;The analyzer runs as a background daemon with a gRPC API. Any service in the infrastructure can send a protocol description and receive analysis results. But the daemon does more than wait for requests - it listens to live traffic.&lt;/p&gt;
&lt;p&gt;The sniffer module captures network traffic (PCAP), reconstructs protocol sessions from packet streams, and feeds them to the symbolic engine in real time. When a TLS handshake happens on the network, the analyzer checks whether that handshake - with its specific cipher suite, extensions, and parameters - satisfies the security properties. When an unusual pattern appears - a repeated nonce, an unexpected message order, a downgrade attempt - it flags the anomaly.&lt;/p&gt;
&lt;p&gt;This is live verification - analysis that runs while the protocol is executing, not after the fact.&lt;/p&gt;
&lt;p&gt;The real-time constraint changes the engineering at its core. Traditional protocol analysis explores the full state space - every possible interleaving, every attacker action. That takes hours. Real-time analysis uses a bounded model: explore the state space up to depth N, report what you find, continue monitoring. The GNN-guided search from our previous article is critical here - it prioritizes the most promising exploration paths, finding attacks faster in bounded time.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;web-interface-collaboration-not-just-output&quot;&gt;Web interface: collaboration, not just output&lt;/h3&gt;
&lt;p&gt;The analyzer ships a web UI. This is a requirement for adoption, not a luxury.&lt;/p&gt;
&lt;p&gt;Security review is a collaborative process. An analyst finds an attack, a protocol designer evaluates the proposed fix, a manager decides whether the risk is acceptable. In existing tools, this workflow happens outside the tool: screenshots in Slack, traces in emails, decisions in meetings.&lt;/p&gt;
&lt;p&gt;The web UI makes the analysis itself collaborative. Attack graphs are rendered in 3D - navigable, zoomable, inspectable. Repair proposals are presented side-by-side with the original protocol, with the differences highlighted. Reviewers can comment, approve, or reject fixes. The entire security review happens inside the platform.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;converters-meeting-people-where-they-are&quot;&gt;Converters: meeting people where they are&lt;/h3&gt;
&lt;p&gt;The analyzer accepts input from ProVerif, Tamarin, Scyther, Avispa, CryptoVerif, and Maude descriptions. This is an adoption strategy, not a convenience feature.&lt;/p&gt;
&lt;p&gt;Thousands of protocols are already modeled in these languages. Researchers and engineers who have invested years in ProVerif models should not have to rewrite them. They import their existing description, run the analyzer, and get the same output plus repair proposals plus real-time monitoring plus CI integration.&lt;/p&gt;
&lt;p&gt;The converters are imperfect - each source language has constructs that do not map cleanly. But they handle the common case well enough to lower the barrier from &quot;I should try this tool&quot; to &quot;I just tried this tool.&quot;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;platform-not-tool&quot;&gt;Platform, not tool&lt;/h3&gt;
&lt;p&gt;Let us be explicit about what we are building. A tool does one thing from the command line. A platform integrates into workflows, provides APIs, runs continuously, and collaborates with other systems.&lt;/p&gt;
&lt;p&gt;The protocol analyzer we are building has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A CLI for direct use and scripting&lt;/li&gt;
&lt;li&gt;A GitHub Action for CI/CD&lt;/li&gt;
&lt;li&gt;An LSP server for IDE integration&lt;/li&gt;
&lt;li&gt;A gRPC API for programmatic access&lt;/li&gt;
&lt;li&gt;A daemon for continuous monitoring&lt;/li&gt;
&lt;li&gt;A sniffer for real-time traffic analysis&lt;/li&gt;
&lt;li&gt;A web UI for collaborative review&lt;/li&gt;
&lt;li&gt;WASM bindings for browser-based playground&lt;/li&gt;
&lt;li&gt;Converters from five existing tools&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a platform. The symbolic analysis core - the part described in the previous article - is the engine. Everything else is the interface between that engine and the world where protocols are built, deployed, and attacked.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-revolution-is-practical-not-technical&quot;&gt;The revolution is practical, not technical&lt;/h3&gt;
&lt;p&gt;The technical contributions matter: automatic repair, GNN-guided search, incremental closure, sixteen attacker models. But the reason they matter is that they enable a new capability - protocol verification that runs in CI, in your IDE, on your network, in real time.&lt;/p&gt;
&lt;p&gt;ProVerif and Tamarin proved that symbolic protocol analysis is possible. We are trying to prove that it is practical - practical enough to run on every commit, practical enough to catch vulnerabilities while you type, practical enough to monitor live traffic.&lt;/p&gt;
&lt;p&gt;That is the shift: from laboratory instrument to industrial platform. From &quot;analyze after design&quot; to &quot;verify during development and after deployment.&quot; From a tool that finds attacks to a platform that finds them, fixes them, and watches for them.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;status&quot;&gt;Status&lt;/h3&gt;
&lt;p&gt;The CI/CD action, the LSP server, the daemon, and the sniffer are in active development. The web UI has working prototypes. The converters handle common cases. The core engine - described in the previous article - is functional and stable.&lt;/p&gt;
&lt;p&gt;This project is under active development. The platform described here is being built, not shipped. But the direction is clear, and the architecture supports it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This project is under active development. This article describes the planned platform capabilities of a tool still being built. The previous article covers the core engine architecture; this one covers the interfaces between that engine and the development lifecycle.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Follow the project:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Sup4ikX&quot;&gt;github.com/Sup4ikX&lt;/a&gt; - a preview release will be published there as soon as the first production version is ready.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24974/the-revolution-in-protocol-security-how-it-actually-gets-used</guid>
<pubDate>Thu, 20 Aug 2026 07:46:14 +0000</pubDate>
</item>
<item>
<title>Blue/green deployments on Kubernetes with Argo Rollouts</title>
<link>https://coderlegion.com/23139/blue-green-deployments-on-kubernetes-with-argo-rollouts</link>
<description>&lt;p&gt;One of the harder questions to answer at scale is how to ship without a few seconds where your users are getting timeouts or your fleet is split across two image versions.&lt;/p&gt;
&lt;p&gt;Kubernetes’s default rolling update strategy gradually deploys new pods and retires old ones, but during the swap, your service runs both versions side by side, and a regression in the new image affects every request that lands on a new pod.&lt;/p&gt;
&lt;p&gt;Progressive delivery patterns like blue/green have long existed: you stand the new version up alongside the old, prove it’s healthy on a separate preview endpoint, then flip user traffic across. Blast radius shrinks to nothing in the bad case, rollback is a single command, and your release stops being a held-breath moment.&lt;/p&gt;
&lt;p&gt;In this post, you’ll set that up with &lt;a rel=&quot;nofollow&quot; href=&quot;https://argoproj.github.io/argo-rollouts/&quot;&gt;Argo Rollouts&lt;/a&gt;, the controller behind progressive delivery in the Argo ecosystem (which graduated from the CNCF in 2022).&lt;/p&gt;
&lt;h3 id=&quot;what-is-argo-rollouts-and-why-you-need-it-&quot;&gt;What is Argo Rollouts (and why you need it)&lt;/h3&gt;
&lt;p&gt;Argo Rollouts is a Kubernetes controller and a set of CRDs that bolt blue/green, canary, and other progressive delivery strategies onto your cluster. The primary CRD is Rollout, a drop-in replacement for the standard Deployment.&lt;/p&gt;
&lt;p&gt;You convert an existing Deployment by changing the apiVersion to argoproj.io/v1alpha1 and the kind to Rollout, then adding a strategy.blueGreen or strategy.canary block that describes how a new revision should roll out.&lt;/p&gt;
&lt;p&gt;A major reason to reach for it is that there’s no built-in way to do this kind of traffic control in Kubernetes (you can’t decide where requests go independently of which pods are Ready). There’s no easy rollback to the previous version once the update has started.&lt;/p&gt;
&lt;p&gt;Argo Rollouts fills in everything around that. It plugs into ingress controllers (Traefik, ALB) and service meshes (Istio, Linkerd, SMI) for real traffic shaping. It can query metrics providers (Prometheus, Datadog, CloudWatch, New Relic) to gate promotions on hard numbers, and it tracks every revision as its own ReplicaSet, so flipping back is instant.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;This tutorial assumes some familiarity with Kubernetes. You’ll also need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A working Kubernetes cluster (EKS, GKE, AKS, or local like Minikube/Kind).&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kubernetes.io/docs/tasks/tools/&quot;&gt;kubectl&lt;/a&gt; installed and pointed at the cluster (kubectl get nodes should return at least one Ready node)&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://helm.sh/docs/intro/install/&quot;&gt;helm&lt;/a&gt; v3 installed&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://curl.se/&quot;&gt;curl&lt;/a&gt; for hitting the demo app&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;step-1-installing-the-argo-rollouts-controller&quot;&gt;Step 1: Installing the Argo Rollouts controller&lt;/h4&gt;
&lt;p&gt;Argo Rollouts ships as a controller that runs in its own namespace, along with a kubectl plugin you’ll use on your laptop to inspect and steer rollouts.&lt;/p&gt;
&lt;p&gt;To install the controller, follow these steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Add the Argo Helm repo and install the controller:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argo-rollouts argo/argo-rollouts \
  --namespace argo-rollouts \
  --create-namespace \
  --wait
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confirm the controller pods are up:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl -n argo-rollouts get pods
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;NAME                             READY   STATUS    RESTARTS   AGE
argo-rollouts-dcd465dfc-8m2ql    1/1     Running   0          79s
argo-rollouts-dcd465dfc-q92k4    1/1     Running   0          79s
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;step-2-installing-the-kubectl-argo-rollouts-plugin&quot;&gt;Step 2: Installing the kubectl argo rollouts plugin&lt;/h4&gt;
&lt;p&gt;The controller is running, but the most ergonomic way to drive a rollout — inspecting state, setting images, promoting, rolling back — is the kubectl argo rollouts plugin. It’s a separate binary that drops onto your PATH, and kubectl picks it up automatically.&lt;/p&gt;
&lt;p&gt;To install the plugin, follow these steps:&lt;/p&gt;
&lt;p&gt;On macOS with Homebrew:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install argoproj/tap/kubectl-argo-rollouts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On Linux:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -sLO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64
chmod +x kubectl-argo-rollouts-linux-amd64
sudo mv kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify it’s wired up:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl argo rollouts version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see something like &lt;code&gt;kubectl-argo-rollouts: v1.8.3+...&lt;/code&gt;. From here on, we’ll use kubectl argo rollouts … subcommands to drive the rollout.&lt;/p&gt;
&lt;h4 id=&quot;step-3-defining-the-rollout&quot;&gt;Step 3: Defining the Rollout&lt;/h4&gt;
&lt;p&gt;Argo Rollouts’ core idea is that you swap your Deployment for a Rollout resource. The pod template inside it is identical to a Deployment’s.&lt;/p&gt;
&lt;p&gt;What changes is the spec.strategy block, which describes how a new revision should roll out.&lt;/p&gt;
&lt;p&gt;For blue/green, you need three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A Rollout with &lt;code&gt;spec.strategy.blueGreen&lt;/code&gt; configured&lt;/li&gt;
&lt;li&gt;An active Service, which always points to whichever ReplicaSet is currently serving production traffic&lt;/li&gt;
&lt;li&gt;A preview Service, which points at the new ReplicaSet before it gets promoted, so that you can test it in isolation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Argo Rollouts injects the rollouts-pod-template-hash label into each Service’s selector at runtime, which is how it switches traffic without you ever editing the Services.&lt;/p&gt;
&lt;p&gt;Write the manifest:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat &amp;gt; rollout.yaml &amp;lt;&amp;lt;'EOF'
apiVersion: v1
kind: Service
metadata:
  name: rollouts-demo-active
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
  selector:
    app: rollouts-demo
---
apiVersion: v1
kind: Service
metadata:
  name: rollouts-demo-preview
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
  selector:
    app: rollouts-demo
---
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: rollouts-demo
spec:
  replicas: 2
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: rollouts-demo
  template:
    metadata:
      labels:
        app: rollouts-demo
    spec:
      containers:
        - name: rollouts-demo
          image: argoproj/rollouts-demo:blue
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
              protocol: TCP
          resources:
            requests:
              cpu: 25m
              memory: 32Mi
  strategy:
    blueGreen:
      activeService: rollouts-demo-active
      previewService: rollouts-demo-preview
      autoPromotionEnabled: false
      scaleDownDelaySeconds: 30
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few fields are worth calling out in the &lt;code&gt;blueGreen&lt;/code&gt; block:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;activeService&lt;/code&gt; and &lt;code&gt;previewService&lt;/code&gt; are the names of the two ClusterIP Services above. Argo Rollouts owns their selectors from here on; you don’t edit them by hand.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;autoPromotionEnabled&lt;/code&gt;: false is what makes this a manual promotion. The new ReplicaSet comes up, you inspect it on the preview Service, and only then do you flip the active Service over. Set it to true (the default) and Argo will auto-promote the moment the new pods are Ready.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scaleDownDelaySeconds&lt;/code&gt;: 30 keeps the old (blue) ReplicaSet around for 30 seconds after promotion, so if something goes wrong in those first few seconds, you can flip back instantly without rescheduling pods.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apply it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl apply -f rollout.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We’re using &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/argoproj/rollouts-demo&quot;&gt;argoproj/rollouts-demo&lt;/a&gt;, a tiny app published by the Argo team that serves an HTML dashboard and a /color endpoint that reports which tagged image is running (blue, green, yellow, etc.). It’s perfect for seeing the cutover happen in real time.&lt;/p&gt;
&lt;h4 id=&quot;step-4-checking-the-initial-state&quot;&gt;Step 4: Checking the initial state&lt;/h4&gt;
&lt;p&gt;Take a look at the rollout:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl argo rollouts get rollout rollouts-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Name:            rollouts-demo
Namespace:       default
Status:          ✔ Healthy
Strategy:        BlueGreen
Images:          argoproj/rollouts-demo:blue (stable, active)
Replicas:
  Desired:       2
  Current:       2
  Updated:       2
  Ready:         2
  Available:     2

NAME                                       KIND        STATUS     AGE  INFO
⟳ rollouts-demo                            Rollout     ✔ Healthy  34s
└──# revision:1
   └──⧉ rollouts-demo-86c957c6d6           ReplicaSet  ✔ Healthy  34s  stable,active
      ├──□ rollouts-demo-86c957c6d6-72kjf  Pod         ✔ Running  34s  ready:1/1
      └──□ rollouts-demo-86c957c6d6-nv3zg  Pod         ✔ Running  34s  ready:1/1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One revision, two pods, both stable and active. Both Services currently point at the same ReplicaSet hash. You can confirm with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl get svc rollouts-demo-active rollouts-demo-preview \
  -o jsonpath='{range .items[*]}{.metadata.name}{&quot; -&amp;gt; hash=&quot;}{.spec.selector.rollouts-pod-template-hash}{&quot;\n&quot;}{end}'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;rollouts-demo-active -&amp;gt; hash=86c957c6d6
rollouts-demo-preview -&amp;gt; hash=86c957c6d6
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-5-triggering-a-new-version&quot;&gt;Step 5: Triggering a new version&lt;/h4&gt;
&lt;p&gt;Now let’s deploy a new revision. We’ll change the image tag from blue to yellow:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl argo rollouts set image rollouts-demo \
  rollouts-demo=argoproj/rollouts-demo:yellow
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Argo creates a new ReplicaSet (rev 2) for the yellow image and waits, because we set autoPromotionEnabled: false. The active Service still points at blue. The preview Service is re-pointed at yellow:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl argo rollouts get rollout rollouts-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Status:          ॥ Paused
Message:         BlueGreenPause
Strategy:        BlueGreen
Images:          argoproj/rollouts-demo:blue (stable, active)
                 argoproj/rollouts-demo:yellow (preview)
Replicas:
  Desired:       2
  Current:       4
  Updated:       2
  Ready:         2
  Available:     2

NAME                                       KIND        STATUS     AGE  INFO
⟳ rollouts-demo                            Rollout     ॥ Paused
├──# revision:2
│  └──⧉ rollouts-demo-7cf9dff6bb           ReplicaSet  ✔ Healthy  38s  preview
│     ├──□ rollouts-demo-7cf9dff6bb-cbp2c  Pod         ✔ Running  38s  ready:1/1
│     └──□ rollouts-demo-7cf9dff6bb-fn2gt  Pod         ✔ Running  38s  ready:1/1
└──# revision:1
   └──⧉ rollouts-demo-86c957c6d6           ReplicaSet  ✔ Healthy  5m   stable,active
      ├──□ rollouts-demo-86c957c6d6-72kjf  Pod         ✔ Running  5m   ready:1/1
      └──□ rollouts-demo-86c957c6d6-nv3zg  Pod         ✔ Running  5m   ready:1/1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the heart of blue/green. The cluster is now running both versions, but only blue is serving real traffic.&lt;/p&gt;
&lt;h4 id=&quot;step-6-proving-the-split-with-curl&quot;&gt;Step 6: Proving the split with curl&lt;/h4&gt;
&lt;p&gt;Forward both Services to your laptop on different local ports:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl port-forward svc/rollouts-demo-active 8080:80 &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;
kubectl port-forward svc/rollouts-demo-preview 8081:80 &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;
sleep 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hit each one using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;echo &quot;active : $(curl -s http://127.0.0.1:8080/color)&quot;
echo &quot;preview: $(curl -s http://127.0.0.1:8081/color)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;active : &quot;blue&quot;
preview: &quot;yellow&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is exactly the window where you’d run smoke tests, point a staging frontend at the preview hostname, or have Argo run an AnalysisTemplate against Prometheus.&lt;/p&gt;
&lt;p&gt;Nothing about production traffic has changed yet.&lt;/p&gt;
&lt;p&gt;When you’re done with the port-forwards, run the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kill %1 %2 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-7-promoting&quot;&gt;Step 7: Promoting&lt;/h4&gt;
&lt;p&gt;When you’re happy, flip the active Service over with one command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl argo rollouts promote rollouts-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;rollout 'rollouts-demo' promoted
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Argo updates the active Service’s selector to the new ReplicaSet hash.&lt;/p&gt;
&lt;p&gt;Subsequent requests should show production traffic is in yellow. The old blue pods stick around for scaleDownDelaySeconds (30 by default) before being torn down, which is what makes the next section possible.&lt;/p&gt;
&lt;p&gt;Confirm the cutover by running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl argo rollouts status rollouts-demo --timeout 60s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see Healthy, and the Service selectors should now agree:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;kubectl get svc rollouts-demo-active rollouts-demo-preview \
  -o jsonpath='{range .items[*]}{.metadata.name}{&quot; -&amp;gt; hash=&quot;}{.spec.selector.rollouts-pod-template-hash}{&quot;\n&quot;}{end}'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;rollouts-demo-active -&amp;gt; hash=7cf9dff6bb
rollouts-demo-preview -&amp;gt; hash=7cf9dff6bb
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-8-rolling-back&quot;&gt;Step 8: Rolling back&lt;/h4&gt;
&lt;p&gt;If something goes wrong after the promotion (a metric tanks, you spot an error in the logs, a teammate flags a bug), undo it by running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl argo rollouts undo rollouts-demo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That brings the previous ReplicaSet back as the new “preview” and pauses, waiting for you to confirm with &lt;code&gt;promote&lt;/code&gt; once more, which flips the active Service back to it. Because the old pods were kept warm by scaleDownDelaySeconds, this happens in seconds, not whatever your image pull time is.&lt;/p&gt;
&lt;h4 id=&quot;step-9-cleaning-up&quot;&gt;Step 9: Cleaning up&lt;/h4&gt;
&lt;p&gt;Once you’re done, you can tear down the demo using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl delete -f rollout.yaml
helm uninstall argo-rollouts -n argo-rollouts
kubectl delete ns argo-rollouts
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;how-does-this-fit-with-octopus-deploy-and-argo-cd&quot;&gt;How does this fit with Octopus Deploy and Argo CD&lt;/h3&gt;
&lt;p&gt;Everything we’ve done so far works on its own. You’ve got a Rollout, two Services, and a one-command promote/undo loop.&lt;/p&gt;
&lt;p&gt;Argo Rollouts is happy to do its job at the cluster level. What it doesn’t have is an opinion on how dev becomes staging and then becomes production. Who’s allowed to push the button, or what the deployment history looked like six weeks ago.&lt;/p&gt;
&lt;p&gt;This is the layer &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/&quot;&gt;Octopus Deploy&lt;/a&gt; is built for. A good mental model is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Argo Rollouts owns the cluster-side mechanics&lt;/strong&gt;: Which ReplicaSet is active, which is preview, when to flip, and when to scale down old pods.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Argo CD owns the GitOps sync&lt;/strong&gt;: The Rollout (and its Services) live in a Git repo, and the cluster state is reconciled to match.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Octopus owns everything above that&lt;/strong&gt;: Environments, approval gates, release lifecycles, audit trails, and the self-service UI that developers actually click on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The promotion path between environments is described once in Octopus and reused across every service, instead of being re-encoded in each team’s CI script.&lt;/p&gt;
&lt;h3 id=&quot;ship-green-sleep-through-the-night&quot;&gt;Ship green, sleep through the night&lt;/h3&gt;
&lt;p&gt;If you made it this far, you’ve got the cluster-side mechanics of progressive delivery sorted: a Rollout flipping between active and preview Services, a manual promotion gate, and instant rollback. That’s the hard, hands-on layer done.&lt;/p&gt;
&lt;p&gt;What’s missing is the orchestration above it, including environments, approvals, audit trails, and the self-service flow your developers actually click. That’s where &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/&quot;&gt;Octopus Deploy&lt;/a&gt; slots in, sitting on top of &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/blog/argo-cd-in-octopus&quot;&gt;Argo CD&lt;/a&gt; and Argo Rollouts to give you a complete progressive delivery stack across every environment, not just one cluster.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/docs/argo-cd&quot;&gt;Connect your Argo CD instance to Octopus&lt;/a&gt; and see how the whole pipeline comes together, or &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/start&quot;&gt;try Octopus free&lt;/a&gt; and wire it up against your own cluster.&lt;/p&gt;
&lt;p&gt;Happy deployments!&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/23139/blue-green-deployments-on-kubernetes-with-argo-rollouts</guid>
<pubDate>Tue, 21 Jul 2026 13:03:23 +0000</pubDate>
</item>
<item>
<title>When Lighthouse CI maintenance in CI/CD pipelines becomes a second job</title>
<link>https://coderlegion.com/21891/when-lighthouse-ci-maintenance-in-ci-cd-pipelines-becomes-a-second-job</link>
<description>&lt;p&gt;Performance budget gates in GitHub Actions are free until someone owns twelve client workflows, Chrome drift, and client reporting. How agencies split Lighthouse CI from managed PageSpeed monitoring.&lt;/p&gt;
&lt;p&gt;The Slack thread started with a screenshot of a green GitHub Actions run. By the third reply someone had pasted a Lighthouse JSON artefact, a link to a Chrome release note, and a question nobody wanted to own: &quot;Which client repository still pins Lighthouse 10?&quot;&lt;/p&gt;
&lt;p&gt;That is the week Lighthouse CI stopped being a merge gate and became a second job. The pipeline still passed and the portfolio still needed evidence, but the difference was who paid in hours: the developer shipping a feature, or the one person who inherited every &lt;code&gt;lighthouserc&lt;/code&gt; file across client repositories.&lt;/p&gt;
&lt;h3 id=&quot;when-does-lighthouse-ci-maintenance-outgrow-a-ci-cd-pipeline-&quot;&gt;When does Lighthouse CI maintenance outgrow a CI/CD pipeline?&lt;/h3&gt;
&lt;p&gt;Lighthouse CI earns its place early. You wire assertions on a preview URL, block a CLS regression, and the team trusts the red build. The cost is front-loaded configuration, not ongoing calendar time.&lt;/p&gt;
&lt;p&gt;The shift happens when success creates obligations a CI/CD pipeline was never designed to carry:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every new client repository needs a copied workflow, pinned Chrome, and preview URL rules that match their host.&lt;/li&gt;
&lt;li&gt;Assertions need tuning after flaky LCP on cold runners, so thresholds loosen until they barely catch real regressions.&lt;/li&gt;
&lt;li&gt;Account managers ask for client-ready reports, and the only export is a CI/CD artefact someone must turn into slides.&lt;/li&gt;
&lt;li&gt;Production URLs outside the two preview paths regress while the job stays green.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At that point you are not &quot;running Lighthouse CI in a pipeline.&quot; You are operating a small internal product: version pins, runner hygiene, assertion policy, and reporting glue. For a single product team that can be fine. For an agency portfolio it competes with billable delivery.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-know-lighthouse-ci-in-ci-cd-became-an-unpaid-side-role-&quot;&gt;How do you know Lighthouse CI in CI/CD became an unpaid side role?&lt;/h3&gt;
&lt;p&gt;We treat these as signals to shrink CI/CD scope or add a managed monitoring layer, not as moral failure. Teams hit them around five to fifteen client sites, sometimes sooner when preview hosts differ wildly.&lt;/p&gt;
&lt;h4 id=&quot;flaky-lighthouse-ci-runs-on-github-actions&quot;&gt;Flaky Lighthouse CI runs on GitHub Actions&lt;/h4&gt;
&lt;p&gt;Engineers merge after the third &quot;Re-run failed jobs&quot; click because INP moved six points on a shared runner. When the gate is noise, people route around it.&lt;/p&gt;
&lt;h4 id=&quot;-lhci-cli-and-lighthouse-version-drift-across-repositories&quot;&gt;@lhci/cli and Lighthouse version drift across repositories&lt;/h4&gt;
&lt;p&gt;One repository uses &lt;code&gt;@lhci/cli&lt;/code&gt; 0.13, another 0.14, Lighthouse 11 on one client and 10 on another. Nobody can say which assertion set is authoritative without opening six pull requests.&lt;/p&gt;
&lt;h4 id=&quot;preview-url-versus-production-green-ci-cd-red-pagespeed-insights&quot;&gt;Preview URL versus production: green CI/CD, red PageSpeed Insights&lt;/h4&gt;
&lt;p&gt;The CI/CD job guards &lt;code&gt;/&lt;/code&gt; on a Netlify preview while production serves &lt;code&gt;/checkout&lt;/code&gt; from a different CDN rule. Sponsors see red on PageSpeed Insights; developers point at a green pipeline run and both sides are partially right.&lt;/p&gt;
&lt;h4 id=&quot;lighthouse-ci-artefacts-instead-of-client-ready-performance-reports&quot;&gt;Lighthouse CI artefacts instead of client-ready performance reports&lt;/h4&gt;
&lt;p&gt;Monthly retainers need a short narrative: which URLs broke budget, when, and what changed. If the answer lives in downloaded JSON and a manual chart, someone is doing monitoring work outside the pipeline, unpaid.&lt;/p&gt;
&lt;h4 id=&quot;one-engineer-as-the-default-lighthouse-ci-maintainer&quot;&gt;One engineer as the default Lighthouse CI maintainer&lt;/h4&gt;
&lt;p&gt;One senior developer becomes &quot;the Lighthouse person,&quot; fixes workflows between client calls, and resents every Chrome update. That is a job description, not a YAML tweak.&lt;/p&gt;
&lt;p&gt;If three or more patterns sound familiar, Lighthouse CI is already a second job. The fix is not deleting the pipeline check. It is deciding which hours belong in merge protection and which belong in scheduled monitoring with client-ready output.&lt;/p&gt;
&lt;h3 id=&quot;which-performance-budget-checks-should-lighthouse-ci-enforce-in-ci-cd-&quot;&gt;Which performance budget checks should Lighthouse CI enforce in CI/CD?&lt;/h3&gt;
&lt;p&gt;We keep the CI/CD slice thin on purpose. Merge protection should answer a narrow question: did this candidate build break budgets on the one or two preview URLs we trust for this repository?&lt;/p&gt;
&lt;p&gt;Keep as errors when the check is stable and the developer on the pull request can fix the cause:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CLS on in-scope templates where layout is the likely regression.&lt;/li&gt;
&lt;li&gt;Bundle or script byte ceilings when the build exposes an artefact.&lt;/li&gt;
&lt;li&gt;Total blocking time or main-thread proxies when the change touches JavaScript entry points.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Warn, do not block, when preview parity is weak: cold CDN LCP, ads on staging, INP without enough repetitions to trust a median.&lt;/p&gt;
&lt;p&gt;Do not ask CI/CD to replace production monitoring: field CrUX exports, full sitemap crawls, rank proxies, or every revenue template on every docs-only commit. Those checks belong on a schedule against production URLs, with alerts that account managers can forward.&lt;/p&gt;
&lt;p&gt;The wiring patterns, assertion keys, and GitHub Actions sketch live in our step-by-step guide: &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/how-to-set-up-performance-budgets-in-ci-cd-pipelines?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=hashnode-lighthouse-ci-cicd-second-job&quot;&gt;How to set up performance budgets in CI/CD pipelines&lt;/a&gt;. Use that guide to implement a small gate; use the criteria above to decide how small.&lt;/p&gt;
&lt;h3 id=&quot;lighthouse-ci-versus-managed-monitoring-why-agencies-hit-build-versus-buy-pressure-early&quot;&gt;Lighthouse CI versus managed monitoring: why agencies hit build-versus-buy pressure early&lt;/h3&gt;
&lt;p&gt;Lighthouse CI is open source and runner-native, which makes it the default first step. Managed PageSpeed monitoring adds invoice line items, which makes teams delay the decision until the hidden invoice arrives: senior hours spent keeping workflows honest.&lt;/p&gt;
&lt;p&gt;The build path scales poorly across repositories because CI/CD is repository-centric and agencies are portfolio-centric. Copying configuration per client multiplies drift. Preview URL changes when a client moves hosts. Authentication on staging breaks collection until someone scripts login. Each exception lands on the same maintainer.&lt;/p&gt;
&lt;p&gt;The buy path trades some control for repetition: scheduled lab runs across many URLs, history, thresholds shared with client reports, and alerts when production templates cross budget. It does not replace merge gates on preview; it absorbs the work a pipeline should never have carried: full URL lists, post-deploy cadence, and evidence sponsors can read without terminal access.&lt;/p&gt;
&lt;p&gt;We wrote the comparison for agency leads evaluating that trade explicitly: &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/lighthouse-ci-vs-managed-monitoring-build-vs-buy-agencies?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=hashnode-lighthouse-ci-cicd-second-job&quot;&gt;Lighthouse CI versus managed monitoring: build versus buy for agencies&lt;/a&gt;. The headline question is not &quot;free versus paid.&quot; It is whether your team wants to operate monitoring infrastructure or operate client sites.&lt;/p&gt;
&lt;h3 id=&quot;how-to-shrink-lighthouse-ci-scope-in-ci-cd-pipelines-without-losing-merge-protection&quot;&gt;How to shrink Lighthouse CI scope in CI/CD pipelines without losing merge protection&lt;/h3&gt;
&lt;p&gt;When CI/CD became a second job for us, the recovery plan had four moves. None required removing Lighthouse from the pipeline entirely.&lt;/p&gt;
&lt;h4 id=&quot;limit-lighthouse-ci-to-two-preview-urls-per-client-repository&quot;&gt;Limit Lighthouse CI to two preview URLs per client repository&lt;/h4&gt;
&lt;p&gt;Two preview paths maximum on small retainers: homepage plus one revenue or app-shell template agreed in the statement of work. Everything else lives in scheduled monitoring.&lt;/p&gt;
&lt;h4 id=&quot;one-performance-budget-document-for-ci-cd-gates-and-client-reports&quot;&gt;One performance budget document for CI/CD gates and client reports&lt;/h4&gt;
&lt;p&gt;Copy numbers from the same budget document monitoring uses. If CI/CD gates and the client report disagree on thresholds, engineers ignore both.&lt;/p&gt;
&lt;h4 id=&quot;demote-flaky-lcp-to-warnings-keep-cls-and-bundle-size-as-errors&quot;&gt;Demote flaky LCP to warnings; keep CLS and bundle size as errors&lt;/h4&gt;
&lt;p&gt;Demote flaky LCP to warnings until preview matches production behaviour on paper. Keep CLS and bundle ceilings as errors people act on.&lt;/p&gt;
&lt;h4 id=&quot;document-pre-merge-ci-cd-versus-post-deploy-monitoring-in-the-readme&quot;&gt;Document pre-merge CI/CD versus post-deploy monitoring in the README&lt;/h4&gt;
&lt;p&gt;CI/CD answers pre-merge; monitoring answers post-deploy. Write that split in the repository README so account teams stop asking the pipeline to prove checkout on Friday's release.&lt;/p&gt;
&lt;p&gt;Layer managed monitoring beside the CI/CD job rather than inflating YAML until every pull request waits ten minutes on Chrome. Multi-tenant monitors exist precisely because portfolio evidence does not belong inside each client's GitHub Actions bill.&lt;/p&gt;
&lt;h3 id=&quot;lighthouse-ci-in-ci-cd-versus-scheduled-pagespeed-monitoring-agency-split-for-one-repository&quot;&gt;Lighthouse CI in CI/CD versus scheduled PageSpeed monitoring: agency split for one repository&lt;/h3&gt;
&lt;p&gt;For a typical agency client on a modern JavaScript stack, we aim for under ten minutes of CI/CD web performance time and zero manual JSON exports per month.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Phase &lt;/th&gt;&lt;th&gt;Tooling &lt;/th&gt;&lt;th&gt;Question&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Pre-merge &lt;/td&gt;&lt;td&gt;Lighthouse CI plus one bundle guard &lt;/td&gt;&lt;td&gt;Did this branch regress CLS or script weight on the two preview URLs in scope?&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Post-deploy &lt;/td&gt;&lt;td&gt;Scheduled PageSpeed monitoring &lt;/td&gt;&lt;td&gt;Are production templates still inside budget on the cadence we promised?&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Reporting &lt;/td&gt;&lt;td&gt;Monitoring product export &lt;/td&gt;&lt;td&gt;What do we send the sponsor without opening a terminal?&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Green in CI/CD and red in the Friday report means the split failed, not that someone lied. Either preview parity is wrong, the URL list in monitoring is right and the pipeline was too narrow, or the regression shipped through a path CI/CD never watched. All three are operational fixes, not reasons to add a fourth Lighthouse run on every push.&lt;/p&gt;
&lt;h3 id=&quot;audit-lighthouse-ci-maintenance-before-you-copy-the-workflow-to-another-client&quot;&gt;Audit Lighthouse CI maintenance before you copy the workflow to another client&lt;/h3&gt;
&lt;p&gt;Pick the client repository where the CI/CD job failed most often last quarter. Answer honestly:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Who last updated &lt;code&gt;lighthouserc&lt;/code&gt; and the Chrome pin?&lt;/li&gt;
&lt;li&gt;How many hours went to flaky re-runs versus feature work?&lt;/li&gt;
&lt;li&gt;Which production URL regressed while the pipeline stayed green?&lt;/li&gt;
&lt;li&gt;What did the sponsor receive that was not a raw artefact?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the maintainer has a name and the sponsor answer is &quot;a screenshot from PageSpeed Insights,&quot; you already have a second job. Implement the thin CI/CD template from &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/how-to-set-up-performance-budgets-in-ci-cd-pipelines?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=hashnode-lighthouse-ci-cicd-second-job&quot;&gt;performance budgets in CI/CD pipelines&lt;/a&gt;, then read &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/lighthouse-ci-vs-managed-monitoring-build-vs-buy-agencies?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=hashnode-lighthouse-ci-cicd-second-job&quot;&gt;Lighthouse CI versus managed monitoring&lt;/a&gt; before copying the workflow into client seven.&lt;/p&gt;
&lt;p&gt;Merge gates should block merges you would roll back. Everything else belongs on a schedule, with owners, on the full URL list the retainer names.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.hashnode.dev/lighthouse-ci-maintenance-cicd-pipelines-second-job&quot;&gt;Hashnode&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21891/when-lighthouse-ci-maintenance-in-ci-cd-pipelines-becomes-a-second-job</guid>
<pubDate>Sat, 04 Jul 2026 08:27:36 +0000</pubDate>
</item>
<item>
<title>Ninja is Slow. We Built a Build System in Go That Outperforms It by 39x on a Quarter-Million Tasks</title>
<link>https://coderlegion.com/21836/ninja-is-slow-we-built-build-system-in-go-that-outperforms-it-by-39x-on-quarter-million-tasks</link>
<description>&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=2975149926680001067&quot; alt=&quot;ForgeZero Speed Pipeline&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;forgezero-2-0-we-built-a-build-system-39x-faster-than-ninja&quot;&gt;ForgeZero 2.0: We Built a Build System 39x Faster Than Ninja&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;And yes, it's open source. No bullshit, just architecture.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-everyone-ignores&quot;&gt;The Problem Everyone Ignores&lt;/h3&gt;
&lt;p&gt;You've been there. Sitting at your desk, coffee getting cold, waiting for your build to finish. 10 seconds. 30 seconds. A minute.&lt;/p&gt;
&lt;p&gt;CMake parses its DSL. Ninja forks 2,000 processes. Make thrashes the disk. GNU Autotools exists, and everyone pretends not to notice.&lt;/p&gt;
&lt;p&gt;This isn't a feature gap. This is &lt;strong&gt;architectural malpractice&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Every major build system was designed in an era when parallelism meant &quot;spawn processes.&quot; That's it. No thought given to the actual cost—fork syscalls, context switching, memory duplication, I/O bottlenecks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We decided to fix it properly.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-forgezero-actually-is&quot;&gt;What ForgeZero Actually Is&lt;/h3&gt;
&lt;p&gt;ForgeZero is a cross-platform build system for C, C++, Objective-C, and Assembly. Written in Go. Single binary. No configuration files needed.&lt;/p&gt;
&lt;p&gt;Point it at a directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz build -o binary
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It detects your source files, selects compilers (GCC, Clang, Zig), picks assemblers (NASM, GAS, FASM, or embedded), and outputs production-ready binaries, libraries, or ISO images.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That's literally it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No Makefile. No CMakeLists.txt. No autotools scripts. One command. Full parallelism. Reproducible builds by default.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/forgezero-cli/forgezero&quot;&gt;https://github.com/forgezero-cli/forgezero&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;License:&lt;/strong&gt; GPLv3 (fully open source)&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-benchmark-that-broke-ninja-s-narrative&quot;&gt;The Benchmark That Broke Ninja's Narrative&lt;/h3&gt;
&lt;p&gt;Real project. 2,000 object files. 1,000 .c source files. Real hardware.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ForgeZero:  258 ms  (User: 348ms, System: 520ms)
Ninja:      10.1 s  (User: 73.9s, System: 47.8s)

Speedup: 39.2x ✓
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This isn't synthetic. This isn't a contrived test case. This is what happens when you link 2,000 real object files on a real codebase.&lt;/p&gt;
&lt;p&gt;The gap &lt;strong&gt;grows with scale:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;File Count &lt;/th&gt;&lt;th&gt;ForgeZero &lt;/th&gt;&lt;th&gt;Ninja &lt;/th&gt;&lt;th&gt;Speedup&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1,000     &lt;/td&gt;&lt;td&gt;~85ms     &lt;/td&gt;&lt;td&gt;~2.7s &lt;/td&gt;&lt;td&gt;32x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2,000     &lt;/td&gt;&lt;td&gt;~258ms    &lt;/td&gt;&lt;td&gt;~10.1s&lt;/td&gt;&lt;td&gt;39x&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;10,000    &lt;/td&gt;&lt;td&gt;~1.2s     &lt;/td&gt;&lt;td&gt;~50s  &lt;/td&gt;&lt;td&gt;~42x&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Ninja's overhead scales &lt;strong&gt;linearly&lt;/strong&gt; with file count. ForgeZero's scales &lt;strong&gt;logarithmically&lt;/strong&gt;. The larger your codebase, the more you win.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Watch the actual benchmark:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://t.me/wienton/176&quot;&gt;https://t.me/wienton/176&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-demolishes-every-competitor&quot;&gt;Why This Demolishes Every Competitor&lt;/h3&gt;
&lt;h4 id=&quot;1-architecture-one-process-infinite-goroutines&quot;&gt;1. Architecture: One Process, Infinite Goroutines&lt;/h4&gt;
&lt;p&gt;Traditional systems:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;foreach file:
  fork() → new process
  compile
  wait for completion
  write .o to disk
  read .o back for linking
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ForgeZero:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;spawn N goroutines (microseconds, not milliseconds)
all 2000 files compile in parallel
all objects stay in memory
linker reads from memory arena
zero context switching overhead
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The difference:&lt;/strong&gt; Goroutine creation = microseconds. Process fork = milliseconds. At 2,000 files, you're talking about &lt;strong&gt;1-2 seconds&lt;/strong&gt; saved just on process overhead.&lt;/p&gt;
&lt;h4 id=&quot;2-zero-allocations-in-hot-paths&quot;&gt;2. Zero Allocations in Hot Paths&lt;/h4&gt;
&lt;p&gt;This is the part other build systems don't even think about.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BenchmarkLinkerHotPath:  0 allocs/op, 0 B/op
BenchmarkCodegenHotPath: 0 allocs/op, 0 B/op
BenchmarkHashBLAKE3:     0 allocs/op, 0 B/op
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;No garbage collection pauses.&lt;/strong&gt; No &quot;stop-the-world&quot; events. Deterministic latency like C or Rust.&lt;/p&gt;
&lt;p&gt;Achieved through:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pre-allocated arenas for intermediate objects&lt;/li&gt;
&lt;li&gt;Stack buffers for I/O operations&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sync.Pool&lt;/code&gt; for temporary structures&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero &lt;code&gt;make()&lt;/code&gt;, &lt;code&gt;append()&lt;/code&gt;, or &lt;code&gt;new()&lt;/code&gt; in critical sections&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you're linking thousands of files, GC pauses compound. We eliminated them entirely.&lt;/p&gt;
&lt;h4 id=&quot;3-memory-resident-object-cache&quot;&gt;3. Memory-Resident Object Cache&lt;/h4&gt;
&lt;p&gt;Ninja writes every &lt;code&gt;.o&lt;/code&gt; file to disk, then reads it back. Disk I/O at SSD speeds is 500 MB/sec.&lt;/p&gt;
&lt;p&gt;ForgeZero keeps objects in memory: &lt;strong&gt;10+ GB/sec throughput.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;On a 2000-file project, this alone saves &lt;strong&gt;200-300ms&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;4-blake3-instead-of-sha-256&quot;&gt;4. BLAKE3 Instead of SHA-256&lt;/h4&gt;
&lt;p&gt;Caching depends on file hashes. SHA-256 is cryptographically solid but slow.&lt;/p&gt;
&lt;p&gt;BLAKE3 is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;7x faster&lt;/strong&gt; than SHA-256&lt;/li&gt;
&lt;li&gt;Parallel (SIMD accelerated)&lt;/li&gt;
&lt;li&gt;Cryptographically modern&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On 2000 files: SHA-256 (~500ms) vs BLAKE3 (~70ms). That's &lt;strong&gt;430ms saved per rebuild.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;5-custom-lock-free-scheduler&quot;&gt;5. Custom Lock-Free Scheduler&lt;/h4&gt;
&lt;p&gt;ForgeZero doesn't rely on the OS scheduler. It implements its own:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Mutex&lt;/code&gt;, &lt;code&gt;Semaphore&lt;/code&gt;, &lt;code&gt;WaitGroup&lt;/code&gt; (no external dependencies)&lt;/li&gt;
&lt;li&gt;Lock-free ring buffer for task queues (CAS atomics)&lt;/li&gt;
&lt;li&gt;Worker pool with goroutine reuse&lt;/li&gt;
&lt;li&gt;Atomic counters (no mutex contention)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even under load, zero contention. Maximum throughput.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-s-new-in-forgezero-2-0-v5-3-0-&quot;&gt;What's New in ForgeZero 2.0 (v5.3.0)&lt;/h3&gt;
&lt;h4 id=&quot;embedded-x86-assembler&quot;&gt;Embedded x86 Assembler&lt;/h4&gt;
&lt;p&gt;NASM is gone. ForgeZero now includes a native Intel-syntax assembler that generates correct ELF64 object files.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One dependency eliminated&lt;/li&gt;
&lt;li&gt;Works on all platforms (Linux, Windows, macOS)&lt;/li&gt;
&lt;li&gt;Same behavior everywhere&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;iso-image-generation&quot;&gt;ISO Image Generation&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz build -iso -iso-out kernel.iso
qemu-system-x86_64 -cdrom kernel.iso
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For kernel and firmware developers: &lt;strong&gt;no more mkisofs scripts.&lt;/strong&gt; One flag. Bootable ISO. Done.&lt;/p&gt;
&lt;p&gt;Support for &lt;code&gt;-iso-hybrid&lt;/code&gt; (BIOS + UEFI).&lt;/p&gt;
&lt;h4 id=&quot;three-cache-modes&quot;&gt;Three Cache Modes&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz build                  # disk mode (default, persistent)
fz build -cache=ram       # memory mode (CI/CD, ephemeral envs)
fz build -cache=off       # no cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perfect for Docker containers where disk is slow or ephemeral.&lt;/p&gt;
&lt;h4 id=&quot;inline-bash-scripts&quot;&gt;Inline Bash Scripts&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;scripts:
  - bash: echo &quot;Building...&quot;
  - bash: ./configure --prefix=/usr
  - bash: make install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If no shell exists, uses embedded minimal runner (cd, export, direct binary calls).&lt;/p&gt;
&lt;h4 id=&quot;rollback-support&quot;&gt;Rollback Support&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz --rollback              # Revert to previous version
fz --rollback-to v5.2.0    # Revert to specific version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Production-grade versioning. If something breaks, you recover in seconds.&lt;/p&gt;
&lt;h4 id=&quot;rewritten-scheduler&quot;&gt;Rewritten Scheduler&lt;/h4&gt;
&lt;p&gt;Removed external workerpool dependency. Built custom with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Better error aggregation&lt;/li&gt;
&lt;li&gt;Context-based cancellation&lt;/li&gt;
&lt;li&gt;Graceful shutdown&lt;/li&gt;
&lt;li&gt;Full race condition safety&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;rewritten-config-system&quot;&gt;Rewritten Config System&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Low-allocation merging (no unnecessary copies)&lt;/li&gt;
&lt;li&gt;Parallel config loading&lt;/li&gt;
&lt;li&gt;Clear merge semantics (strings override, slices deduplicate, maps merge)&lt;/li&gt;
&lt;li&gt;Safe defaults with validation&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;cross-platform-reality&quot;&gt;Cross-Platform Reality&lt;/h3&gt;
&lt;p&gt;Single codebase. Three platforms. Identical behavior.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Linux:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Direct syscalls via &lt;code&gt;golang.org/x/sys/unix&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SYS_OPENAT&lt;/code&gt;, &lt;code&gt;SYS_UNLINKAT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;No libc&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Windows:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Native implementation via &lt;code&gt;golang.org/x/sys/windows&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CreateFile&lt;/code&gt;, &lt;code&gt;ReadFile&lt;/code&gt;, &lt;code&gt;WriteFile&lt;/code&gt; APIs&lt;/li&gt;
&lt;li&gt;Retry logic for file locks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;macOS:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full Darwin syscall support&lt;/li&gt;
&lt;li&gt;Identical behavior to Linux&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;VFS Layer&lt;/strong&gt; abstracts all OS differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Path/separator normalization&lt;/li&gt;
&lt;li&gt;TOCTOU attack prevention&lt;/li&gt;
&lt;li&gt;Symlink boundary enforcement (security by default)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;built-in-security-not-bolted-on-&quot;&gt;Built-In Security (Not Bolted On)&lt;/h3&gt;
&lt;h4 id=&quot;fz-audit-sast-scanner&quot;&gt;fz audit — SAST Scanner&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz audit              # Scan for all issues
fz audit --strict     # Strict mode
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Detects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Secrets (AWS keys, GitHub tokens, API keys)&lt;/li&gt;
&lt;li&gt;Dangerous patterns (gets, sprintf without bounds, alloca in loops)&lt;/li&gt;
&lt;li&gt;License violations (GPL/AGPL in proprietary projects)&lt;/li&gt;
&lt;li&gt;Known CVEs in dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;fz-sbom-software-bill-of-materials&quot;&gt;fz sbom — Software Bill of Materials&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz sbom --output sbom.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CycloneDX format with BLAKE3 hashes for every component. Essential for aerospace, medical, and fintech systems.&lt;/p&gt;
&lt;h4 id=&quot;fz-verify-source-integrity&quot;&gt;fz verify — Source Integrity&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz verify --manifest .fz.manifest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Checks every file against BLAKE3 manifest. Detects corruption and supply chain attacks.&lt;/p&gt;
&lt;h4 id=&quot;--reproducible-bit-for-bit-reproducibility&quot;&gt;--reproducible — Bit-for-Bit Reproducibility&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz build --reproducible
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two builds of identical code produce &lt;strong&gt;identical binaries&lt;/strong&gt; (down to the last byte):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Build ID suppressed&lt;/li&gt;
&lt;li&gt;Timestamps zeroed&lt;/li&gt;
&lt;li&gt;Paths normalized&lt;/li&gt;
&lt;li&gt;Order deterministic&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verifiable that no one modified your source.&lt;/p&gt;
&lt;h4 id=&quot;fz-doctor-environment-diagnostics&quot;&gt;fz doctor — Environment Diagnostics&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Checks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Compiler availability and versions&lt;/li&gt;
&lt;li&gt;File access permissions&lt;/li&gt;
&lt;li&gt;Platform integrity&lt;/li&gt;
&lt;li&gt;Required dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Human-readable report of what works and what doesn't.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;supported-everything&quot;&gt;Supported Everything&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Category &lt;/th&gt;&lt;th&gt;Details&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Languages &lt;/td&gt;&lt;td&gt;C, C++, Objective-C, Assembly (NASM/GAS/FASM), Gloria (custom bare-metal language)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Output &lt;/td&gt;&lt;td&gt;ELF, PE, Mach-O, static libraries, shared libraries, flat binaries, ISO images&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Cross-Compilation &lt;/td&gt;&lt;td&gt;ARM (32/64), RISC-V, WebAssembly, Windows, full support via Zig toolchain&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Tools &lt;/td&gt;&lt;td&gt;&lt;code&gt;-compile-commands&lt;/code&gt; (LSP), &lt;code&gt;-watch&lt;/code&gt; (file monitoring), &lt;code&gt;-shell&lt;/code&gt; (REPL), &lt;code&gt;-bench&lt;/code&gt; (profiling), &lt;code&gt;-debug&lt;/code&gt; (DWARF symbols)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Advanced &lt;/td&gt;&lt;td&gt;Bare-metal linking, custom linker scripts, freestanding environments, BIOS+UEFI support&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;package-manager-fz-pm-&quot;&gt;Package Manager (fz pm)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;fz pm add &amp;lt;repo&amp;gt;[@version]    # Add from Git
fz pm remove &amp;lt;package&amp;gt;         # Remove
fz pm list                     # List installed
fz pm catalog                  # Browse official catalog
fz pm search &amp;lt;query&amp;gt;           # Search
fz pm install &amp;lt;name&amp;gt;           # Install with BLAKE3 verification
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All packages verified with BLAKE3 before installation. &lt;strong&gt;No npm dependency hell.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-numbers-don-t-lie&quot;&gt;The Numbers Don't Lie&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;976 commits&lt;/strong&gt; in 2 months (16 commits/day—this is sprint-grade work)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;42,048 lines of code&lt;/strong&gt; (32,275 Go, rest tests + docs)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;234 Go files&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;90%+ test coverage&lt;/strong&gt; on critical packages&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0 allocations&lt;/strong&gt; in hot paths (benchmarked)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;100% golangci-lint&lt;/strong&gt; in strict mode&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Passes all -race checks&lt;/strong&gt; (no data races)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Works on Linux, Windows, macOS&lt;/strong&gt; with zero code changes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This is not fantasy. This is production-grade code.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;comparison-the-reality-check&quot;&gt;Comparison: The Reality Check&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Feature &lt;/th&gt;&lt;th&gt;Make &lt;/th&gt;&lt;th&gt;Ninja &lt;/th&gt;&lt;th&gt;CMake &lt;/th&gt;&lt;th&gt;ForgeZero&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Config files &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Caching &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;Limited &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;3 modes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Parallelism &lt;/td&gt;&lt;td&gt;Processes &lt;/td&gt;&lt;td&gt;Processes &lt;/td&gt;&lt;td&gt;Processes &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Goroutines&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Zero-alloc paths &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Embedded assembler &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ISO images &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Package manager &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;SAST/SBOM &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Cross-compilation &lt;/td&gt;&lt;td&gt;Limited &lt;/td&gt;&lt;td&gt;Limited &lt;/td&gt;&lt;td&gt;Limited &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Full&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Speed&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Baseline &lt;/td&gt;&lt;td&gt;Fast &lt;/td&gt;&lt;td&gt;Medium &lt;/td&gt;&lt;td&gt;&lt;strong&gt;39x Ninja&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Windows native &lt;/td&gt;&lt;td&gt;Limited &lt;/td&gt;&lt;td&gt;Limited &lt;/td&gt;&lt;td&gt;Yes &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rollback support &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Reproducible builds &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;One system dominates every dimension.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;system-requirements&quot;&gt;System Requirements&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OS:&lt;/strong&gt; Linux, Windows (native or WSL2), macOS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Architecture:&lt;/strong&gt; amd64, arm64&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optional:&lt;/strong&gt; GCC/Clang (C/C++), Zig (cross-toolchain), Git (package manager)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;For ISO:&lt;/strong&gt; xorriso (separate installation)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything else is &lt;strong&gt;embedded in a single binary.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-matters-the-reality&quot;&gt;Why This Matters: The Reality&lt;/h3&gt;
&lt;h4 id=&quot;speed-changes-workflow&quot;&gt;Speed Changes Workflow&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Before: 10 seconds per rebuild = context lost
After:  250 milliseconds per rebuild = never interrupt flow
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This isn't productivity theater. This is &lt;strong&gt;fundamental work process improvement.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;On a 50-engineer team, if each engineer rebuilds 50 times per day:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ninja:      10s × 50 builds × 50 engineers = 41,666 seconds wasted
ForgeZero:  0.25s × 50 builds × 50 engineers = 1,041 seconds wasted

Saved: 40,625 seconds/day = 11.3 person-hours/day
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;That's literally one full-time engineer's output, reclaimed.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;no-config-no-errors&quot;&gt;No Config = No Errors&lt;/h4&gt;
&lt;p&gt;CMakeLists.txt, Makefile, autotools—these are programming languages with their own syntax, semantics, and pitfalls.&lt;/p&gt;
&lt;p&gt;ForgeZero needs &lt;strong&gt;zero configuration&lt;/strong&gt; to work. YAML if you want customization. That's it.&lt;/p&gt;
&lt;p&gt;New developers don't spend a day figuring out the build system. They &lt;code&gt;fz build&lt;/code&gt;. Done.&lt;/p&gt;
&lt;h4 id=&quot;systems-work-is-irreplaceable&quot;&gt;Systems Work Is Irreplaceable&lt;/h4&gt;
&lt;p&gt;This tool exists because systems programming is &lt;strong&gt;fundamentally different.&lt;/strong&gt; You can't CMake your way out of bare-metal development.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kernel developers need embedded assembly → we built it in&lt;/li&gt;
&lt;li&gt;Firmware developers need ISO images → integrated&lt;/li&gt;
&lt;li&gt;Security-critical projects need SBOM and reproducible builds → bundled&lt;/li&gt;
&lt;li&gt;Embedded devs need cross-compilation → fully supported&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We're not trying to replace Gradle or Maven. We're building the &lt;strong&gt;right tool for systems work.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-roadmap&quot;&gt;The Roadmap&lt;/h3&gt;
&lt;p&gt;We're not done:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linux Kbuild support for kernel compilation&lt;/li&gt;
&lt;li&gt;Enhanced IDE integration (VSCode, CLion)&lt;/li&gt;
&lt;li&gt;Gloria language expansion (custom systems language)&lt;/li&gt;
&lt;li&gt;Further performance optimization (targeting 50x+ speedup)&lt;/li&gt;
&lt;li&gt;Additional architecture support (PowerPC, MIPS)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This is a living project.&lt;/strong&gt; 976 commits in 2 months. Not a one-off. Not a side project. Real engineering work.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;who-this-is-for&quot;&gt;Who This Is For&lt;/h3&gt;
&lt;h4 id=&quot;you-re-a-systems-programmer&quot;&gt;You're a Systems Programmer&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Building kernels, bootloaders, or firmware&lt;/li&gt;
&lt;li&gt;Need deterministic, reproducible builds&lt;/li&gt;
&lt;li&gt;Want security integrated, not bolted on&lt;/li&gt;
&lt;li&gt;Tired of Makefile spaghetti&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This is for you.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;you-manage-a-build-infrastructure&quot;&gt;You Manage a Build Infrastructure&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Your CI/CD pipeline is 60% waiting for builds&lt;/li&gt;
&lt;li&gt;You want to scale without renting more cloud resources&lt;/li&gt;
&lt;li&gt;You need reproducible, verifiable builds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This saves money and time.&lt;/strong&gt;&lt;/p&gt;
&lt;h4 id=&quot;you-care-about-simplicity&quot;&gt;You Care About Simplicity&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;You believe configuration is a source of bugs&lt;/li&gt;
&lt;li&gt;You want tools that work zero-config and scale up&lt;/li&gt;
&lt;li&gt;You respect your developers' time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This respects your choices.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;get-started&quot;&gt;Get Started&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Linux/macOS
curl -sSL https://raw.githubusercontent.com/forgezero-cli/ForgeZero/main/install.sh | bash

# First build
fz build -o my_program

# Watch the speed difference
touch src/main.c &amp;amp;&amp;amp; fz build -o my_program
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Follow updates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://t.me/wienton/176&quot;&gt;https://t.me/wienton/176&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-statement&quot;&gt;The Statement&lt;/h3&gt;
&lt;p&gt;ForgeZero isn't an improvement on Ninja. It's not an optimization of Make. It's &lt;strong&gt;a complete rewrite&lt;/strong&gt; of what a build system should be, informed by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Modern hardware (multi-core, SIMD, large memory)&lt;/li&gt;
&lt;li&gt;Modern languages (Go, with its goroutines and concurrency primitives)&lt;/li&gt;
&lt;li&gt;Real systems work (assembly, bare-metal, cross-compilation)&lt;/li&gt;
&lt;li&gt;Security best practices (reproducible builds, SBOM, SAST)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;This is a challenge to the industry:&lt;/strong&gt; Slow builds aren't inevitable. They're &lt;strong&gt;architectural choices.&lt;/strong&gt; Fix the architecture, and speed fixes itself.&lt;/p&gt;
&lt;p&gt;If you're tired of waiting for builds. If you're exhausted by CMake configuration. If you just want to &lt;strong&gt;code, not configure&lt;/strong&gt;—ForgeZero is for you.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-details&quot;&gt;The Details&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/alexvoste&quot;&gt;Alex Voste&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/forgezero-cli/forgezero&quot;&gt;https://github.com/forgezero-cli/forgezero&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Latest Release:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/forgezero-cli/forgezero/releases/tag/v5.3.0&quot;&gt;v5.3.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;License:&lt;/strong&gt; GPLv3 (fully open source, fully free)&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Fast builds aren't a luxury. They're an engineering necessity. ForgeZero delivers.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Make it so.&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21836/ninja-is-slow-we-built-build-system-in-go-that-outperforms-it-by-39x-on-quarter-million-tasks</guid>
<pubDate>Fri, 03 Jul 2026 13:10:46 +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>I Spent 40 Minutes at 11pm Debugging a Deploy That Wasn't Broken</title>
<link>https://coderlegion.com/21730/i-spent-40-minutes-at-11pm-debugging-a-deploy-that-wasnt-broken</link>
<description>&lt;p&gt;&lt;em&gt;Level: Intermediate · Time: 6 min · Outcome: A deploy that waits for its dependencies instead of racing them&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I once spent forty minutes at eleven at night debugging a deploy that wasn't broken. The release script ran the database migration, the migration threw &lt;code&gt;connection refused&lt;/code&gt;, the script exited non-zero, the deploy rolled itself back, and I got paged.&lt;/p&gt;
&lt;p&gt;So I did the things you do. I read the migration. I read the logs. I checked the database — it was up, it was healthy, it accepted my connection instantly. I re-ran the deploy and it worked. I chalked it up to gremlins and went to bed, which is the part I'm not proud of, because it happened again two days later. That time I watched the timing: the script brought up a fresh database container and started the migration about six seconds before Postgres finished initializing and began accepting connections. The migration was racing the database's boot. Most of the time it won. The times it lost, I lost forty minutes.&lt;/p&gt;
&lt;p&gt;The script wasn't wrong about anything except one assumption: that a dependency is ready the instant you ask for it.&lt;/p&gt;
&lt;h3 id=&quot;in-production-dependencies-are-eventually-ready&quot;&gt;In production, dependencies are eventually ready&lt;/h3&gt;
&lt;p&gt;That's the mental model shift. Networks blip. A service you call returns a 503 for the two seconds it takes to finish a rolling restart. An API rate-limits you with a 429 it fully expects you to retry. A fresh container's database isn't accepting connections for its first few seconds. Treating the first failure as fatal turns every one of these normal, transient conditions into a paged engineer — and the script that handles them isn't smarter — it declines to give up on the first try.&lt;/p&gt;
&lt;p&gt;But retrying naively is its own trap. Retry instantly and you hammer a recovering service into staying down. Retry forever and a genuinely dead dependency hangs your script indefinitely. Retry a 404 and you wait a minute to confirm what you already knew. Good retries are bounded, backed off, and selective.&lt;/p&gt;
&lt;h3 id=&quot;a-retry-function-you-can-reuse-anywhere&quot;&gt;A retry function you can reuse anywhere&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash
# Purpose: survive transient failures instead of dying on the first error
set -euo pipefail

CHECK=&quot;✓&quot;
CROSS=&quot;✗&quot;

# retry &amp;lt;max_attempts&amp;gt; &amp;lt;command&amp;gt; [args...]
retry() {
    local max_attempts=&quot;$1&quot;; shift
    local attempt=1
    local delay=1            # base delay — doubles each round
    local max_delay=30       # cap so the backoff never runs away

    until &quot;$@&quot;; do
        if (( attempt &amp;gt;= max_attempts )); then
            echo &quot;$CROSS '$*' failed after $attempt attempts&quot; &amp;gt;&amp;amp;2
            return 1
        fi
        # 0–2s of jitter so parallel callers don't all retry on the same beat
        local pause=$(( delay + RANDOM % 3 ))
        echo &quot;$CROSS attempt $attempt failed — retrying in ${pause}s&quot; &amp;gt;&amp;amp;2
        sleep &quot;$pause&quot;
        attempt=$(( attempt + 1 ))
        delay=$(( delay * 2 ))
        (( delay &amp;gt; max_delay )) &amp;amp;&amp;amp; delay=$max_delay
    done

    echo &quot;$CHECK '$*' succeeded on attempt $attempt&quot;
}

# The actual fix for my 11pm deploy: wait for Postgres to accept connections.
retry 6 nc -z -w 2 db.internal 5432
echo &quot;$CHECK database reachable — running migration&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The whole engine is &lt;code&gt;until &quot;$@&quot;; do ... done&lt;/code&gt;. &lt;code&gt;until&lt;/code&gt; runs the command and executes the loop body only when it fails, exiting the instant it succeeds. Passing the command as &lt;code&gt;&quot;$@&quot;&lt;/code&gt; (after &lt;code&gt;shift&lt;/code&gt;-ing past the attempt count) means the function retries anything — a &lt;code&gt;curl&lt;/code&gt;, an &lt;code&gt;ssh&lt;/code&gt;, a port check, your own script — without caring what it is.&lt;/p&gt;
&lt;p&gt;The backoff is the three lines at the bottom of the loop: sleep for the current delay plus a little jitter, then double the delay, capped at &lt;code&gt;max_delay&lt;/code&gt;. That gives you 1s, 2s, 4s, 8s, 16s, 30s, 30s…&lt;/p&gt;
&lt;h3 id=&quot;the-jitter-is-not-decoration&quot;&gt;The jitter is not decoration&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;RANDOM % 3&lt;/code&gt; looks trivial, and it's the line people delete to &quot;clean up.&quot; Don't. Without jitter, a fleet of machines that all failed at the same instant — because the same service went down — will all retry at the same instant, and the same instant after that, producing a synchronized thundering herd that knocks the recovering service straight back over on every round. A few hundred milliseconds of randomness per client spreads the retries out so the service actually gets room to recover. At one machine it does nothing; at fifty it's the difference between recovery and a retry storm.&lt;/p&gt;
&lt;h3 id=&quot;the-mistake-that-makes-retries-dangerous&quot;&gt;The mistake that makes retries dangerous&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Good: a transient failure that retrying can fix
retry 6 nc -z -w 2 db.internal 5432
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Bad: retrying a deterministic failure just delays the error 30 seconds
retry 6 curl -fsS https://api.example.com/v1/thing-that-returns-404
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A retry loop is only as smart as what you point it at. A port check belongs in a loop because the answer changes — &quot;no&quot; until the database boots, then &quot;yes.&quot; A request that returns 404 returns 404 on attempt one and attempt six; the loop just postpones the failure and buries the real status under retry noise. Retry transient failures — timeouts, connection-refused, 429, 5xx, DNS hiccups. Don't retry deterministic ones — a 404, a 401, a syntax error, a missing file. When you can, branch on the exit code or HTTP status and loop only on the codes worth looping on.&lt;/p&gt;
&lt;p&gt;For plain &lt;code&gt;curl&lt;/code&gt;, its built-in &lt;code&gt;--retry 5 --retry-delay 2&lt;/code&gt; does most of this and is simpler; reach for the function when the thing you're retrying isn't curl, or when you want one backoff policy across a database probe, an ssh call, and a download at once.&lt;/p&gt;
&lt;h3 id=&quot;back-to-11pm&quot;&gt;Back to 11pm&lt;/h3&gt;
&lt;p&gt;That deploy never paged me again once the migration waited for the port instead of assuming it. The database still took its six seconds to boot, the network still blipped occasionally — retrying didn't make the dependencies faster. It stopped a normal, transient slowness from being treated as a fatal error, which is most of what &quot;production-ready&quot; means for a script.&lt;/p&gt;
&lt;p&gt;Full function with the wait-for-port pattern and the guidance on which failures to retry: &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/snippets/bash-retry-with-backoff&quot;&gt;https://bashsnippets.xyz/snippets/bash-retry-with-backoff&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Retries are the third leg of an unattended job: &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/snippets/bash-flock-single-instance&quot;&gt;flock&lt;/a&gt; stops overlap, &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/snippets/bash-timeout-command&quot;&gt;timeout&lt;/a&gt; stops hangs, retry rides out the blip. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/tools/cron-wrapper-generator&quot;&gt;Hardened Cron Wrapper Generator&lt;/a&gt; wires all three into one wrapper, &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/guides/bash-scripts-that-survive-cron&quot;&gt;Bash Scripts That Survive Cron&lt;/a&gt; is the end-to-end version, and the rest of the library is at &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz&quot;&gt;https://bashsnippets.xyz&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;Originally published at &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/snippets/bash-retry-with-backoff&quot;&gt;bashsnippets.xyz&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21730/i-spent-40-minutes-at-11pm-debugging-a-deploy-that-wasnt-broken</guid>
<pubDate>Thu, 02 Jul 2026 03:27:52 +0000</pubDate>
</item>
<item>
<title>Building Quantinium: A Focus on Production-Ready AI Systems</title>
<link>https://coderlegion.com/21720/building-quantinium-a-focus-on-production-ready-ai-systems</link>
<description>&lt;p&gt;I recently worked on Quantinium (&lt;a rel=&quot;nofollow&quot; href=&quot;https://quantinium.uk&quot;&gt;https://quantinium.uk&lt;/a&gt;), a project focused on building structured, production-ready AI systems with a strong emphasis on reliability and system design.&lt;/p&gt;
&lt;p&gt;The core experience reinforced a key principle: real-world AI engineering is less about model experimentation and more about system architecture. I focused on designing stable pipelines, structured data flow, and predictable outputs instead of relying on prompt-driven behavior.&lt;/p&gt;
&lt;p&gt;Key work included building modular AI workflows, enforcing output consistency, and optimizing backend integration for scalable performance.&lt;/p&gt;
&lt;p&gt;This project strengthened my approach to building AI systems that prioritize clarity, stability, and real-world usability over complexity.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21720/building-quantinium-a-focus-on-production-ready-ai-systems</guid>
<pubDate>Wed, 01 Jul 2026 15:51:29 +0000</pubDate>
</item>
<item>
<title>The Pipeline Was Green for Three Weeks. It Had Been Shipping a Build That Never Compiled.</title>
<link>https://coderlegion.com/20433/the-pipeline-was-green-for-three-weeks-it-had-been-shipping-a-build-that-never-compiled</link>
<description>&lt;p&gt;For three weeks a deployment pipeline reported every step green and shipped a build that had failed to compile on every single run. The build step ended in &lt;code&gt;npm run build | tee build.log&lt;/code&gt; so the output could be archived. That pipe is the whole story: bash returns the exit status of the &lt;em&gt;last&lt;/em&gt; command in a pipeline, which was &lt;code&gt;tee&lt;/code&gt;, and &lt;code&gt;tee&lt;/code&gt; always succeeds at copying text. The compiler's non-zero exit got thrown away the instant the pipe handed off. The error was sitting right there in &lt;code&gt;build.log&lt;/code&gt;. GitHub Actions saw exit code 0, painted the step green, and deployed the broken artifact. Nobody read the log, because the checkmark said there was nothing to read.&lt;/p&gt;
&lt;p&gt;That's the defining property of bash in CI, and it's why I treat pipeline scripts differently from anything I run in a terminal: &lt;strong&gt;a silent failure can present as success.&lt;/strong&gt; On a server you watch a command fail in front of you. In a pipeline, a swallowed exit code produces a green checkmark over broken code, and the gap between &quot;the logs show an error&quot; and &quot;the pipeline reports failure&quot; is exactly where outages are born. I wrote the full guide because I've now been burned by every variation of this, and there's a consistent set of habits that close the gap.&lt;/p&gt;
&lt;p&gt;There are four failure modes that are specific to CI and barely ever bite you at an interactive prompt. &lt;strong&gt;Exit codes swallowed by a pipe&lt;/strong&gt; — the story above, any &lt;code&gt;command | tee&lt;/code&gt;, &lt;code&gt;command | grep&lt;/code&gt;, &lt;code&gt;command | sort&lt;/code&gt;. &lt;strong&gt;Shell provisioning differences&lt;/strong&gt; — &lt;code&gt;ubuntu-latest&lt;/code&gt; gives you bash 5.x, &lt;code&gt;macos-latest&lt;/code&gt; gives you bash 3.2 from 2007, and a script using associative arrays or &lt;code&gt;${var,,}&lt;/code&gt; passes on one runner and throws a syntax error on the other in the same workflow. &lt;strong&gt;Environment variable gaps&lt;/strong&gt; — CI sets variables you don't control and omits ones you assume exist, and without &lt;code&gt;set -u&lt;/code&gt; a missing &lt;code&gt;$DEPLOY_TARGET&lt;/code&gt; becomes an empty string and does something quietly wrong. &lt;strong&gt;Interactive-shell assumptions&lt;/strong&gt; — CI runs a non-interactive, non-login shell that never sources your &lt;code&gt;.bashrc&lt;/code&gt;, so a command that works when you type it dies with &lt;code&gt;command not found&lt;/code&gt; because the thing that defined it was never loaded.&lt;/p&gt;
&lt;p&gt;The header that closes most of these is short, and every line earns its place:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;set -e&lt;/code&gt; exits the moment any command fails, so the step exits non-zero and the workflow actually registers a failure. &lt;code&gt;set -u&lt;/code&gt; treats an unset variable as an error, so a typo'd &lt;code&gt;$DPLOY_TARGET&lt;/code&gt; dies immediately instead of expanding to nothing and corrupting a path. &lt;code&gt;set -o pipefail&lt;/code&gt; makes a pipeline return the first non-zero exit among its commands rather than only the last — that one flag is the direct fix for the &lt;code&gt;| tee&lt;/code&gt; bug that ran green for three weeks.&lt;/p&gt;
&lt;p&gt;Secrets deserve their own paragraph because CI hands you &lt;code&gt;env:&lt;/code&gt; values and &lt;code&gt;secrets:&lt;/code&gt; values identically — the shell can't tell them apart, the only difference is that Actions masks the secret's literal string in the log. The trap is that the moment you transform a secret (base64-decode it, slice it, interpolate it), the transformed value no longer matches the mask and prints in clear text. Validate required values up front with &lt;code&gt;${VAR:?}&lt;/code&gt; so a missing secret fails at startup with a clear message instead of on line 47 with a cryptic &lt;code&gt;permission denied&lt;/code&gt;, and be very careful with &lt;code&gt;set -x&lt;/code&gt; in any step that touches a secret.&lt;/p&gt;
&lt;p&gt;The pipe-exit-code problem is worth one concrete tool beyond &lt;code&gt;pipefail&lt;/code&gt;: &lt;code&gt;PIPESTATUS&lt;/code&gt; is an array holding the exit code of every command in the last pipeline, read immediately after:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run build | tee build.log
build_rc=&quot;${PIPESTATUS[0]}&quot;   # npm's code, not tee's
[[ &quot;$build_rc&quot; -eq 0 ]] || { echo &quot;build failed: $build_rc&quot; &amp;gt;&amp;amp;2; exit &quot;$build_rc&quot;; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pipefail&lt;/code&gt; has one well-known false positive — &lt;code&gt;grep&lt;/code&gt; returns 1 when it finds no matches, which is often fine, and under &lt;code&gt;pipefail&lt;/code&gt; plus &lt;code&gt;set -e&lt;/code&gt; that aborts the script. Absorb it deliberately with &lt;code&gt;|| true&lt;/code&gt; only where a non-match is genuinely acceptable, and nowhere else, because blanketing every command in &lt;code&gt;|| true&lt;/code&gt; just reinvents the silent-success problem you're trying to kill.&lt;/p&gt;
&lt;p&gt;Docker has its own landmine: every entrypoint script must end with &lt;code&gt;exec &quot;$@&quot;&lt;/code&gt;. Without it, your script stays PID 1 and your app runs as a child, so when the orchestrator sends SIGTERM on &lt;code&gt;docker stop&lt;/code&gt; or a rolling deploy, the signal hits the &lt;em&gt;script&lt;/em&gt;, which doesn't forward it, and after the grace period the orchestrator escalates to SIGKILL — abrupt termination, dropped connections, lost in-flight work. &lt;code&gt;exec &quot;$@&quot;&lt;/code&gt; replaces the shell with your app so it &lt;em&gt;becomes&lt;/em&gt; PID 1 and receives signals directly. The guide pairs this with a &lt;code&gt;wait_for&lt;/code&gt; dependency-check pattern and a trap, and the &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/tools/bash-trap-builder&quot;&gt;Bash trap &amp;amp; Signal Handler Builder&lt;/a&gt; generates the exact signal block an entrypoint needs.&lt;/p&gt;
&lt;p&gt;Deploys get the same treatment: deploy into a fresh timestamped directory, flip a &lt;code&gt;current&lt;/code&gt; symlink atomically with &lt;code&gt;ln -sfn&lt;/code&gt; so traffic never sees a half-written release, keep the last several releases so rollback is just re-pointing the symlink, run a health check after the swap and fail the deploy if it doesn't pass, and stamp the git SHA into the release so &quot;what's running right now&quot; always has an answer. And when a step fails and the logs won't say why, &lt;code&gt;set -x&lt;/code&gt; around just the suspect section shows you each command with its variables expanded — a doubled slash or an empty segment in the trace is usually your bug standing in plain sight.&lt;/p&gt;
&lt;p&gt;The full guide is the field manual version of all of this — the four failure modes, the safe header, secret validation with a &lt;code&gt;validate_env&lt;/code&gt; function, &lt;code&gt;PIPESTATUS&lt;/code&gt; and &lt;code&gt;pipefail&lt;/code&gt;, Docker entrypoints, the atomic-symlink deploy script with rollback and health check, debugging with &lt;code&gt;set -x&lt;/code&gt;, and a production-ready checklist at the end: &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/guides/bash-scripting-for-ci-cd-pipelines&quot;&gt;https://bashsnippets.xyz/guides/bash-scripting-for-ci-cd-pipelines&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If your pipeline scripts are dying on the small stuff first — unquoted loops, bad argument parsing, missing traps — the snippet library that feeds into this guide is at &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz&quot;&gt;https://bashsnippets.xyz&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20433/the-pipeline-was-green-for-three-weeks-it-had-been-shipping-a-build-that-never-compiled</guid>
<pubDate>Fri, 12 Jun 2026 16:25:56 +0000</pubDate>
</item>
<item>
<title>What performance checks belong in CI (and what belongs in monitoring)</title>
<link>https://coderlegion.com/20111/what-performance-checks-belong-in-ci-and-what-belongs-in-monitoring</link>
<description>&lt;p&gt;Lighthouse CI can block merges or become a job teams ignore. Here is how we split pre-merge checks, bundle limits, and scheduled PageSpeed monitoring so CI only tests what it can do reliably.&lt;/p&gt;
&lt;p&gt;A pull request passed Lighthouse CI on Friday and the preview URL looked fine, but on Monday scheduled monitoring flagged checkout INP on the same release.&lt;/p&gt;
&lt;p&gt;The merge was not obviously wrong. The pipeline had checked the homepage and a marketing template. The regression was on a route the workflow never tested. That gap is normal once you track more than a handful of URLs, which is why we stopped using CI as a stand-in for production monitoring and limited it to a small set of pre-merge checks.&lt;/p&gt;
&lt;p&gt;Below is what we put in the pipeline, what we keep in scheduled PageSpeed monitoring, and what we removed because it only produced failed builds that teams stopped trusting.&lt;/p&gt;
&lt;h3 id=&quot;when-a-green-ci-build-still-breaks-production-webperf&quot;&gt;When a green CI build still breaks production webperf&lt;/h3&gt;
&lt;p&gt;CI answers one question: did this candidate build cross the thresholds we set on the URLs we test in a lab environment?&lt;/p&gt;
&lt;p&gt;Production monitoring answers another: are the URLs clients care about still inside budget after deploy, next week, and on templates we never added to the workflow file?&lt;/p&gt;
&lt;p&gt;The problems we see most often:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CI tests the wrong URLs: it guards &lt;code&gt;/&lt;/code&gt; and &lt;code&gt;/pricing&lt;/code&gt; while commerce runs on &lt;code&gt;/checkout&lt;/code&gt; and &lt;code&gt;/cart&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Preview differs from production: CDN rules, auth, feature flags, or third-party scripts differ between preview hosts and live domains.&lt;/li&gt;
&lt;li&gt;Unstable runs: LCP and INP swing run to run on cold CI runners; strict errors on metrics that vary run to run train teams to ignore the job.&lt;/li&gt;
&lt;li&gt;Too much in one pipeline: the job tries to enforce field CrUX, SEO rank proxies, or twelve templates on every docs-only PR.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The fix is not more Lighthouse runs on every push. It is choosing checks CI can run reliably and moving the rest to monitoring, with a written rule for who tests what after deploy.&lt;/p&gt;
&lt;h3 id=&quot;performance-budget-ci-gates-what-we-block-on-merge&quot;&gt;Performance budget CI gates: what we block on merge&lt;/h3&gt;
&lt;p&gt;We treat performance budget CI gates as hard stops on merge or deploy preview, not as a report for executives.&lt;/p&gt;
&lt;p&gt;Block (error) when the check is stable and linked to what the PR changed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CLS and layout stability on the templates in scope (lab, but repeatable).&lt;/li&gt;
&lt;li&gt;Total blocking time or max potential FID proxies when the change touches main-thread JavaScript.&lt;/li&gt;
&lt;li&gt;Resource summary limits when bundle size is the likely regression (script bytes, third-party count).&lt;/li&gt;
&lt;li&gt;Performance category floor only when the team has stable reference scores on the preview host; otherwise it becomes a warning first.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Warn when the metric is useful but unstable until preview scores stabilise:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LCP on preview URLs with ads, A/B snippets, or cold CDN edges.&lt;/li&gt;
&lt;li&gt;INP unless the PR touches interaction-heavy UI and you run enough repetitions to trust the median.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do not gate in CI (monitor instead):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Field CrUX or Search Console exports (belong after deploy, not in CI).&lt;/li&gt;
&lt;li&gt;Rank or traffic proxies.&lt;/li&gt;
&lt;li&gt;Every template in the sitemap on every commit.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Copy the numbers from the same policy doc you use in monitoring. Our Watcher guide on &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/the-complete-guide-to-performance-budgets-for-web-teams?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=hashnode-ci-gates-webperf&quot;&gt;performance budgets for web teams&lt;/a&gt; defines the metrics and limits; CI should enforce a subset, not a second spreadsheet.&lt;/p&gt;
&lt;h3 id=&quot;resource-and-bundle-budgets-what-we-enforce-before-lighthouse-runs&quot;&gt;Resource and bundle budgets: what we enforce before Lighthouse runs&lt;/h3&gt;
&lt;p&gt;Lab scores can stay green while JavaScript weight climbs every sprint. We enforce at least one resource check that does not depend on Lighthouse score swings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Main bundle size ceiling (webpack &lt;code&gt;performance&lt;/code&gt;, &lt;code&gt;size-limit&lt;/code&gt;, or &lt;code&gt;bundlesize&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Dependency diff review when a PR adds more than an agreed chunk of kilobytes.&lt;/li&gt;
&lt;li&gt;Image budget on static imports when LCP regressions usually trace to hero assets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Resource gates are fast, deterministic, and easy to explain in a PR comment (&quot;main bundle +42 KB&quot;). They catch the regressions developers actually introduce. Lighthouse catches how those bytes behave on a throttled mobile profile.&lt;/p&gt;
&lt;p&gt;Order matters: run fast checks first. There is little value in a five-minute Lighthouse run when a bundle guard would have stopped the merge in thirty seconds.&lt;/p&gt;
&lt;h3 id=&quot;lighthouse-ci-vs-scheduled-monitoring-split-the-pipeline-jobs&quot;&gt;Lighthouse CI vs scheduled monitoring: split the pipeline jobs&lt;/h3&gt;
&lt;p&gt;Run Lighthouse CI on the path that produces a candidate URL: build, preview deploy, health check, then &lt;code&gt;lhci autorun&lt;/code&gt; (or collect + assert). Run it on PRs to protected branches and on release tags, with path filters so markdown-only changes do not run Lighthouse unless you want them to.&lt;/p&gt;
&lt;p&gt;Run scheduled PageSpeed monitoring after merge: production or staging URLs, client URL groups, a test schedule you can state in a client report, alerts when budgets break in production.&lt;/p&gt;
&lt;p&gt;We document the split in onboarding:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Phase &lt;/th&gt;&lt;th&gt;Owner &lt;/th&gt;&lt;th&gt;Question&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Pre-merge &lt;/td&gt;&lt;td&gt;CI (LHCI + bundle guards) &lt;/td&gt;&lt;td&gt;Did this branch break budgets on the URLs we test in preview?&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Post-deploy &lt;/td&gt;&lt;td&gt;Monitoring product &lt;/td&gt;&lt;td&gt;Are production templates still inside budget on the schedule we promised?&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Use the same thresholds in both places. If CI and monitoring use different numbers, engineers ignore both.&lt;/p&gt;
&lt;p&gt;For wiring LHCI, assertions, and GitHub Actions patterns, the step-by-step lives on our blog: &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/how-to-set-up-performance-budgets-in-ci-cd-pipelines?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=hashnode-ci-gates-webperf&quot;&gt;How to set up performance budgets in CI/CD pipelines&lt;/a&gt;. This post is the decision guide before you paste YAML.&lt;/p&gt;
&lt;h3 id=&quot;performance-budgets-in-ci-for-agency-client-repositories&quot;&gt;Performance budgets in CI for agency client repositories&lt;/h3&gt;
&lt;p&gt;Agencies rarely maintain one pipeline. Client A is Shopify with theme checks; Client B is Next.js on Vercel; Client C still ships WordPress with a plugin stack nobody wants to touch in CI.&lt;/p&gt;
&lt;p&gt;Our default agency CI template is intentionally small:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;One or two preview URLs the client agrees are highest risk (often homepage plus one revenue template; two LHCI URLs max on small retainers).&lt;/li&gt;
&lt;li&gt;One bundle or size guard when the stack exposes a build artefact.&lt;/li&gt;
&lt;li&gt;Written rule in the README: after deploy, monitoring watches the full URL list the SOW names.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We do not paste the client's full performance budget table into every repo. Procurement and account teams already struggle when monitoring scope is vague; CI scope should be narrower and written in the README (&quot;LHCI runs on these two preview URLs; full template list is in Watcher&quot;).&lt;/p&gt;
&lt;p&gt;Multi-tenant monitoring tracks many sites over time. CI blocks merges when preview tests show a clear regression on the paths in scope.&lt;/p&gt;
&lt;h3 id=&quot;ci-webperf-gates-we-moved-to-warnings-or-removed&quot;&gt;CI webperf gates we moved to warnings or removed&lt;/h3&gt;
&lt;p&gt;After several quarters when the CI job failed too often, we changed policy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Removed full-site crawls inside CI (flaky, slow, duplicates monitoring).&lt;/li&gt;
&lt;li&gt;Removed failing PRs on single-run LCP without &lt;code&gt;numberOfRuns&lt;/code&gt; ≥ 2.&lt;/li&gt;
&lt;li&gt;Moved performance score on preview to warn until preview parity with production was documented.&lt;/li&gt;
&lt;li&gt;Moved INP on long-tail templates and logged-in routes to monitoring only unless the PR touches that flow.&lt;/li&gt;
&lt;li&gt;Kept CLS regressions on in-scope templates and bundle ceilings on main entry points as errors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When the main branch fails every other day, teams bypass the job. Fewer error rules that people act on work better than dozens of metrics.&lt;/p&gt;
&lt;h3 id=&quot;internal-checklist-before-adding-another-ci-gate&quot;&gt;Internal checklist before adding another CI gate&lt;/h3&gt;
&lt;p&gt;Before we add a new webperf step to a client pipeline, we ask:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Does this metric fail for reasons the developer on the PR can fix?&lt;/li&gt;
&lt;li&gt;Is the preview URL stable enough to trust an error (not just a warn)?&lt;/li&gt;
&lt;li&gt;Is the same threshold already monitored post-deploy, or will CI create a second set of numbers?&lt;/li&gt;
&lt;li&gt;Will the job finish in time for the team's PR review workflow (under ~10 minutes for small sites)?&lt;/li&gt;
&lt;li&gt;Can we explain the failure in one sentence in the PR comment?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If two or more answers are no, leave that check in monitoring or in a weekly report, not in merge blocking.&lt;/p&gt;
&lt;h3 id=&quot;next-step-pick-three-gates-for-one-repository&quot;&gt;Next step: pick three gates for one repository&lt;/h3&gt;
&lt;p&gt;Open the client repo where CI fails most often, or your own product site. Write three lines in the README:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;URLs Lighthouse CI tests on preview (max two to start).&lt;/li&gt;
&lt;li&gt;Errors: CLS, bundle KB, or TBT; pick one lab metric you trust.&lt;/li&gt;
&lt;li&gt;Warnings: LCP or performance score until preview scores stay stable.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Implement those before adding a fourth check. Pair with the same numeric budgets you use in monitoring so &quot;green in CI&quot; and &quot;green in the report&quot; mean the same thing.&lt;/p&gt;
&lt;p&gt;If you need assertion keys, &lt;code&gt;lighthouserc&lt;/code&gt; shapes, and a GitHub Actions sketch, use &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/how-to-set-up-performance-budgets-in-ci-cd-pipelines?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=hashnode-ci-gates-webperf&quot;&gt;performance budgets in CI/CD pipelines&lt;/a&gt;. If you need the full budget strategy and metric tables first, start with &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/the-complete-guide-to-performance-budgets-for-web-teams?utm_source=hashnode&amp;amp;utm_medium=referral&amp;amp;utm_campaign=hashnode-ci-gates-webperf&quot;&gt;the complete guide to performance budgets&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;CI should block merges you would have rolled back anyway. Run everything else on the full URL list in scheduled monitoring, without making every PR wait on Chrome.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.hashnode.dev/what-performance-checks-belong-in-ci-and-what-belongs-in-monitoring&quot;&gt;Hashnode&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20111/what-performance-checks-belong-in-ci-and-what-belongs-in-monitoring</guid>
<pubDate>Tue, 09 Jun 2026 21:32:39 +0000</pubDate>
</item>
<item>
<title>From Local Tests to CI in One Command</title>
<link>https://coderlegion.com/17576/from-local-tests-to-ci-in-one-command</link>
<description>&lt;h3 id=&quot;why-set-up-ci-early&quot;&gt;Why Set Up CI Early&lt;/h3&gt;
&lt;p&gt;Once you have tests worth running, the natural next step is making sure they run on every push. The sooner you do it, the fewer surprises down the road — things like env vars that only exist locally or paths that work on your machine but nowhere else.&lt;/p&gt;
&lt;p&gt;If you've been following this series, you just ran &lt;code&gt;/twd&lt;/code&gt; to write your first batch of tests. This is the right moment to wire them into CI, while everything is fresh.&lt;/p&gt;
&lt;h3 id=&quot;what-the-skill-actually-does&quot;&gt;What the Skill Actually Does&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;/twd:ci-setup&lt;/code&gt; is part of the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/BRIKEV/twd-ai&quot;&gt;TWD AI plugin&lt;/a&gt;. Like &lt;code&gt;/twd:setup&lt;/code&gt;, it starts with a &lt;strong&gt;discovery phase&lt;/strong&gt; — detecting your project configuration before asking you anything.&lt;/p&gt;
&lt;p&gt;The skill inspects your project to figure out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your framework and build tooling&lt;/li&gt;
&lt;li&gt;The dev server port your app runs on&lt;/li&gt;
&lt;li&gt;Your base path, if you have one configured&lt;/li&gt;
&lt;li&gt;Whether you have coverage already wired up, and if not, whether you want it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most of this is inferred automatically. The main thing it asks you about is coverage. Once it has the full picture, it generates a GitHub Actions workflow tailored to your project.&lt;/p&gt;
&lt;p&gt;Here is an example of what it produces:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: TWD Tests

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

permissions:
  pull-requests: write  # only needed if using contract-report

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-node@v5
        with:
          node-version: 24
          cache: npm

      - name: Install dependencies
        run: npm ci

      - name: Install mock service worker
        run: npx twd-js init public --save

      - name: Start dev server
        run: |
          nohup npm run dev &amp;gt; /dev/null 2&amp;gt;&amp;amp;1 &amp;amp;
          npx wait-on http://localhost:5173

      - name: Run TWD tests
        uses: BRIKEV/twd-cli/.github/actions/run@main
        with:
          contract-report: 'true'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The workflow starts the dev server, waits for it to be ready, and runs your TWD tests using the official &lt;code&gt;twd-cli&lt;/code&gt; GitHub Action. That action handles Puppeteer setup, Chrome installation, and optionally posts a contract validation report as a PR comment. If your app runs on a different port or base path, the skill adjusts the configuration accordingly.&lt;/p&gt;
&lt;h3 id=&quot;running-it&quot;&gt;Running It&lt;/h3&gt;
&lt;p&gt;After you've written tests with &lt;code&gt;/twd&lt;/code&gt;, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;/twd:ci-setup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The skill will work through the detection phase, ask whether you want coverage reporting, then drop the workflow file into &lt;code&gt;.github/workflows/&lt;/code&gt;. Push the branch, open a pull request, and your tests run.&lt;/p&gt;
&lt;p&gt;That first CI run tends to surface the usual suspects: an env var that exists locally but not in CI, a path assumption that only works on your machine, or a dependency that wasn't properly declared. The skill takes care of the pipeline setup — and CI itself takes care of showing you what was missing.&lt;/p&gt;
&lt;h3 id=&quot;the-pattern-this-follows&quot;&gt;The Pattern This Follows&lt;/h3&gt;
&lt;p&gt;If you used &lt;code&gt;/twd:setup&lt;/code&gt; to initialize TWD in your project, this feels familiar. Detection first, minimal questions, concrete output. The philosophy is the same: don't make developers manage configuration when the tooling can figure it out.&lt;/p&gt;
&lt;p&gt;The two commands are designed to be used in sequence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;/twd&lt;/code&gt; — write tests for your components and pages&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/twd:ci-setup&lt;/code&gt; — make sure those tests run automatically on every push&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Neither requires you to know the internals of TWD, GitHub Actions syntax, or coverage tooling. You describe what you want; the skill handles the rest.&lt;/p&gt;
&lt;h3 id=&quot;what-comes-next&quot;&gt;What Comes Next&lt;/h3&gt;
&lt;p&gt;Setting up CI is about making sure your tests run. The next question is a harder one: are you testing the right things? You might have solid coverage on the happy path and nothing on the edge cases that actually break in production.&lt;/p&gt;
&lt;p&gt;The next article in this series covers the TWD Test Gap Analysis skill — how it analyzes your existing tests, identifies what is missing, and suggests what to write next. Not just &quot;you have low coverage on this file&quot;, but a concrete read of what user flows and failure modes are not being tested.&lt;/p&gt;
&lt;p&gt;If you want to follow along or explore the TWD plugin: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/BRIKEV/twd-ai&quot;&gt;https://github.com/BRIKEV/twd-ai&lt;/a&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/17576/from-local-tests-to-ci-in-one-command</guid>
<pubDate>Tue, 12 May 2026 13:46:58 +0000</pubDate>
</item>
<item>
<title>Connecting an AWS EKS Argo CD instance to Octopus Cloud</title>
<link>https://coderlegion.com/17538/connecting-an-aws-eks-argo-cd-instance-to-octopus-cloud</link>
<description>&lt;p&gt;Argo CD has become the go-to tool for adopting GitOps in Kubernetes environments. Here at Octopus Deploy, we are huge fans of it; in fact, we're &lt;a rel=&quot;nofollow&quot; href=&quot;https://insights.linuxfoundation.org/project/argo/contributors?timeRange=past365days&amp;amp;repos=argoproj-argo-cd,argoproj_argo-cd&amp;amp;start=2025-04-24&amp;amp;end=2026-04-24&amp;amp;widget=organizations-leaderboard&quot;&gt;lead maintainers of the Argo project&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In a previous article, we looked at &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/blog/aks-argo-cd-to-self-hosted-octopus&quot;&gt;connecting an Argo CD instance running on a Self-Hosted Azure Kubernetes Service (AKS) to Octopus Deploy&lt;/a&gt;. While this was neat, AWS still leads the cloud infrastructure market at around 29% as of Q3 2025, according to &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.srgresearch.com/articles/cloud-market-share-trends-big-three-together-hold-63-while-oracle-and-the-neoclouds-inch-higher&quot;&gt;Synergy Research Group&lt;/a&gt;, so if you have been wondering how to replicate that tutorial on EKS, you're in the right place.&lt;/p&gt;
&lt;h3 id=&quot;why-connect-argo-cds-on-eks-to-octopus-cloud-&quot;&gt;Why connect Argo CDs on EKS to Octopus Cloud?&lt;/h3&gt;
&lt;p&gt;Running Argo CD on EKS gives you GitOps-driven deployments into Kubernetes, but Argo CD alone does not cover the full delivery picture. On the other end of that picture is Octopus, which coordinates the release process around it: approvals, environment progression, runbooks, and a single view across Kubernetes and everything else you ship.&lt;/p&gt;
&lt;p&gt;Octopus Cloud customers get a mostly plug-and-play experience because Octopus has already configured the communication rules on its side, and the Helm commands generated by the connection wizard handle the rest (assuming no egress restrictions sit in front of the Argo CD cluster).&lt;/p&gt;
&lt;p&gt;In our previous blog, a lot of the steps came about as a result of needing to open up certain ports in order to establish connectivity with your Octopus server; the majority of this goes away when you run in AWS and use Octopus Cloud.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites-1&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;In order to follow along with this portion of the tutorial, you will need the following tools installed locally.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.hashicorp.com/terraform/install&quot;&gt;Terraform&lt;/a&gt;, for provisioning ArgoCD and the Argo CD Gateway onto the cluster&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://helm.sh/docs/intro/install/&quot;&gt;Helm&lt;/a&gt;, for installing the gateway chart&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kubernetes.io/docs/tasks/tools/&quot;&gt;kubectl&lt;/a&gt;, for interacting with the cluster&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://argo-cd.readthedocs.io/en/stable/cli_installation/&quot;&gt;ArgoCD CLI&lt;/a&gt;, for generating an auth token for the Octopus gateway account&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/docs/octopus-rest-api/how-to-create-an-api-key&quot;&gt;An Octopus API key&lt;/a&gt;, for accessing the Octopus Deploy REST API&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;step-1-provision-a-kubernetes-cluster&quot;&gt;Step 1: Provision a Kubernetes cluster&lt;/h3&gt;
&lt;p&gt;If you already have a cluster, feel free to skip to the next section. However, if you don't, the following lines will use the EKS CLI, to create one.&lt;/p&gt;
&lt;p&gt;To follow along, run the following commands:&lt;/p&gt;
&lt;p&gt;Create a cluster configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cat &amp;lt;&amp;lt; 'EOF' &amp;gt; cluster.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: basic-cluster
  region: eu-north-1

nodeGroups:
  - name: ng-1
    instanceType: m5.large
    desiredCapacity: 10
    volumeSize: 80
    ssh:
      allow: true # will use ~/.ssh/id_rsa.pub as the default ssh key
  - name: ng-2
    instanceType: m5.large
    desiredCapacity: 2
    volumeSize: 100
    ssh:
      publicKeyPath: ~/.ssh/id_rsa.pub
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The cluster configuration above will provision two nodes with m5.large as the instance type, feel free to tweak the configuration as you see fit.&lt;/p&gt;
&lt;p&gt;Create the cluster by running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eksctl create cluster -f cluster.yaml --timeout 45m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: The &lt;code&gt;--timeout&lt;/code&gt; flag here is to ensure the CLI does not timeout before the cluster is done being provisioned.&lt;/p&gt;
&lt;p&gt;Finally, you can obtain the Kubeconfig for the cluster created by running:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eksctl utils write-kubeconfig --cluster=basic-cluster --region=eu-north-1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-2-install-argocd&quot;&gt;Step 2: Install ArgoCD&lt;/h3&gt;
&lt;p&gt;The next step is to install ArgoCD. Using the helm chart is the most straightforward means of installing.&lt;/p&gt;
&lt;p&gt;Head back to your terminal and run the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;helm install argocd argo-cd \
  --repo https://argoproj.github.io/argo-helm \
  --create-namespace \
  --namespace argocd \
  --wait \
  --timeout 10m \
  --values - &amp;lt;&amp;lt; 'EOF'
configs:
  cm:
    accounts.octopus: apiKey
  rbac:
    policy.default: &quot;role:readonly&quot;
    policy.csv: |
      g, admin, role:admin
      p, octopus, applications, get, *, allow
      p, octopus, applications, sync, *, allow
      p, octopus, clusters, get, *, allow
      p, octopus, logs, get, */*, allow
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output is similar to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;NAME: argocd
LAST DEPLOYED: Fri Apr 15 14:32:08 2026
NAMESPACE: argocd
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The command will install Argo CD into a namespace called &lt;code&gt;argocd&lt;/code&gt;, the extra values file tacked on sets up the account Octopus will use to talk to it.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;accounts.octopus: apiKey&lt;/code&gt; creates a dedicated &lt;code&gt;octopus&lt;/code&gt; user in Argo CD and enables API key authentication for it, so Octopus has its own identity rather than piggybacking on &lt;code&gt;admin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;rbac&lt;/code&gt; block then scopes what that user can do: &lt;code&gt;policy.default: &quot;role:readonly&quot;&lt;/code&gt; makes read-only the baseline for any account without an explicit grant, and the &lt;code&gt;policy.csv&lt;/code&gt; lines grant the &lt;code&gt;octopus&lt;/code&gt; user only the permissions it needs.&lt;/p&gt;
&lt;p&gt;The values above become relevant once we install the Octopus Argo gateway.&lt;/p&gt;
&lt;h4 id=&quot;what-is-the-octopus-argo-gateway-&quot;&gt;What is the Octopus Argo gateway?&lt;/h4&gt;
&lt;p&gt;Simply put, the Octopus Argo gateway is a component required to establish a secure, TLS-encrypted connection between an Argo CD instance (running in a Kubernetes cluster) and the Octopus Deploy.&lt;/p&gt;
&lt;p&gt;In contrast to self-hosted setups where you need to create firewall rules and potentially consider what certs to use to secure the communication, the Octopus Argo gateway handles it all.&lt;/p&gt;
&lt;h5 id=&quot;generate-an-authentication-token&quot;&gt;Generate an authentication token&lt;/h5&gt;
&lt;p&gt;In order for the gateway to authenticate with the ArgoCD instance you just deployed, an authentication token is required. To create one, run the following commands:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Grab the auto-generated admin password&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ARGOCD_PASSWORD=$(kubectl get secret argocd-initial-admin-secret \
  -n argocd \
  -o jsonpath='{.data.password}' | base64 --decode)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Port-forward the Argo CD server to localhost:18080&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kubectl port-forward svc/argocd-server -n argocd 18080:443 &amp;amp;
PF_PID=$!
sleep 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Log in as admin&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;argocd login localhost:18080 \
  --username admin \
  --password &quot;$ARGOCD_PASSWORD&quot; \
  --insecure \
  --grpc-web
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Mint a token for the octopus account&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;argocd account generate-token \
  --account octopus \
  --insecure \
  --grpc-web
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Be sure to copy and save the token.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Clean up the port-forward&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kill $PF_PID
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-3-link-your-cluster&quot;&gt;Step 3: Link your cluster&lt;/h3&gt;
&lt;p&gt;From here on out, installing the Octopus Argo gateway is only a few clicks.&lt;/p&gt;
&lt;p&gt;Within the sidebar on the left side of your Octopus dashboard, click on the tab &lt;strong&gt;Argo CD Instances.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l9lbvl6sfyxeqxwgge8c.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure 1:&lt;/strong&gt; &lt;em&gt;Octopus dashboard displaying the Argo CD Instances sidebar menu option&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Within the Argo CD instance tab click on &lt;strong&gt;Add Argo Instance.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iw9bdyyuv6ajjqabncwl.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure 2:&lt;/strong&gt; &lt;em&gt;Screenshot of the Argo Instance button&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Up next, you should be greeted with the following menu.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p839umi2o3ool57z8hvf.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure 3:&lt;/strong&gt; &lt;em&gt;Screenshot showing the Argo CD instance registration form&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Enter a name for this cluster's connection, and if you wish you can select an environment for the ArgoCD instance. Leave the DNS name as the default unless you modified it during the Helm install.&lt;/p&gt;
&lt;p&gt;Paste in the authentication token you generated earlier and hit &lt;strong&gt;next.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/88f9vymgvavd15a52yo1.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure 4:&lt;/strong&gt; &lt;em&gt;Configuring a project to deploy with Argo CD&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Select which project you would like to connect your Argo instance to and hit &lt;strong&gt;Configure Project.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Right after the configuration wizard, it will return a Helm install command which looks something like this.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;helm install --atomic \
  --create-namespace --namespace octo-argo-gateway-eks-dev \
  --version &quot;*.*&quot; \
  --set registration.octopus.name=&quot;EKS-dev&quot; \
  --set registration.octopus.serverApiUrl=&quot;https://&amp;lt;your-octopus-instance&amp;gt;.octopus.app/&quot; \
  --set registration.octopus.serverAccessToken=&quot;&amp;lt;your-octopus-server-access-token&amp;gt;&quot; \
  --set registration.octopus.environments=&quot;{development}&quot; \
  --set registration.octopus.spaceId=&quot;Spaces-1&quot; \
  --set gateway.octopus.serverGrpcUrl=&quot;grpc://&amp;lt;your-octopus-instance&amp;gt;.octopus.app:8443&quot; \
  --set gateway.argocd.serverGrpcUrl=&quot;grpc://argocd-server.argocd.svc.cluster.local&quot; \
  --set gateway.argocd.insecure=&quot;true&quot; \
  --set gateway.argocd.plaintext=&quot;false&quot; \
  --set gateway.argocd.authenticationToken=&quot;&amp;lt;your-argocd-token&amp;gt;&quot; \
  eks-dev \
  oci://registry-1.docker.io/octopusdeploy/octopus-argocd-gateway-chart
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Paste the command in your terminal and in a couple of seconds your cluster should be connected!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t5crlc56mb3nze71chd2.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure 5:&lt;/strong&gt; &lt;em&gt;Confirmation of a connected cluster in the Argo CD Instance tab&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;when-do-i-pick-octopus-cloud-over-self-hosted-&quot;&gt;When do I pick Octopus Cloud over self-hosted?&lt;/h3&gt;
&lt;p&gt;If you caught &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/blog/aks-argo-cd-to-self-hosted-octopus&quot;&gt;our last blog on connecting an AKS cluster to a self-hosted Octopus server&lt;/a&gt;, you might be wondering which path fits your setup.&lt;/p&gt;
&lt;p&gt;Octopus Cloud makes sense when you want the connection to just work. The wizard generates the Helm command, you paste a token, and you are done. If your team does not have a strong reason to run Octopus on your own infrastructure, this is a reasonable path.&lt;/p&gt;
&lt;p&gt;Self-hosted Octopus makes sense when the answer to &quot;can the server live in our VPC?&quot; is &quot;it has to.&quot; which usually comes down to data residency rules, existing on-premises infrastructure you need to keep using, or security policies that will not let a third party hold your deployment control plane.&lt;/p&gt;
&lt;p&gt;You pay for that control with the networking work in Shawn's post: opening inbound ports, handling internal DNS, dealing with certs signed by your own CA, and configuring load balancers for gRPC. None of it is hard, but it is work.&lt;/p&gt;
&lt;h3 id=&quot;wherever-your-clusters-live-octopus-fits&quot;&gt;Wherever your clusters live, Octopus fits&lt;/h3&gt;
&lt;p&gt;In this post, we walked through connecting an EKS cluster to Octopus Cloud.&lt;/p&gt;
&lt;p&gt;We also looked at some of the tradeoffs between doing this in a self-hosted environment versus the cloud.&lt;/p&gt;
&lt;p&gt;Whichever path fits your constraints, the outcome is the same. Argo CD keeps doing GitOps. Octopus handles approvals, environment progression, runbooks, and everything outside Kubernetes that you still need to ship.&lt;/p&gt;
&lt;p&gt;Spin up a &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/start&quot;&gt;free Octopus Cloud instance&lt;/a&gt; and try the Argo CD integration for yourself, or &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/lp/schedule-a-demo&quot;&gt;book a demo&lt;/a&gt; if you want a walkthrough.&lt;/p&gt;
&lt;p&gt;Happy deployments!&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/17538/connecting-an-aws-eks-argo-cd-instance-to-octopus-cloud</guid>
<pubDate>Tue, 12 May 2026 09:45:45 +0000</pubDate>
</item>
<item>
<title>7 Signs Your Release Process Is More Fragile Than You Think</title>
<link>https://coderlegion.com/17388/7-signs-your-release-process-is-more-fragile-than-you-think</link>
<description>&lt;p&gt;Most of the release processes look good. Pipelines are green, deploys happen all the time, and nothing is on fire. When something breaks, you only see the cracks, and suddenly you’re trying to answer questions that your tooling doesn’t help with.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dora.dev/guides/dora-metrics/?ref=hackernoon.com&quot;&gt;DORA metrics&lt;/a&gt; are speed and stability. But they don't tell you if rollback works, if approvals live outside of Slack, or if the artifact you tested is the one in production. You can hit all four DORA metrics and still be one bad deploy away from a bad day.&lt;/p&gt;
&lt;p&gt;Here are seven signs your process is more fragile than you realize. Individually they seem small. But they don't hold up well under pressure together.&lt;/p&gt;
&lt;h3 id=&quot;1-who-deployed-this-takes-more-than-a-minute-to-answer&quot;&gt;1. &quot;Who deployed this?&quot; takes more than a minute to answer&lt;/h3&gt;
&lt;p&gt;This is asked almost as soon as a serious incident occurs. If you have to go through CI logs, scroll through Slack, or hope someone remembers to answer your questions, &lt;strong&gt;your release process has no operational memory.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The data is out there somewhere, but it’s not structured as a release record; it’s scattered across pipeline runs, commit messages, conversations. &lt;strong&gt;Running a CI does not capture the actual deployment intent&lt;/strong&gt;, and so engineers open multiple systems during incident calls to reconstruct the course of events.&lt;/p&gt;
&lt;h3 id=&quot;2-the-rollback-lives-in-a-doc-not-in-the-system&quot;&gt;2. The rollback lives in a doc, not in the system&lt;/h3&gt;
&lt;p&gt;A healthy, well-defined rollback scenario is of utmost importance. Most teams talk about having a rollback plan, but hardly anyone’s ever put one to the test during a real incident.&lt;/p&gt;
&lt;p&gt;Here’s what really happens: you scramble to find the old tag, kick off a pipeline, cross your fingers that the environment hasn’t changed, and try to remember all those finicky parameters. Every single step is a chance for things to break. As a post-mortem on approval-gated releases said it directly: “&lt;a rel=&quot;nofollow&quot; href=&quot;https://orbnetes.cloud/blog/reducing-failed-releases-with-approvals-and-automatic-rollbacks&quot;&gt;rollback instructions existed in documents, but not in executable policy.&lt;/a&gt;”&lt;/p&gt;
&lt;p&gt;So you end up staring into that gap, right when pressure’s at its worst. And here is where you start to improvise chaotically.&lt;/p&gt;
&lt;h3 id=&quot;3-deployments-come-down-to-whoever-s-got-access-and-enough-urgency-to-use-it&quot;&gt;3. Deployments come down to whoever’s got access and enough urgency to use it&lt;/h3&gt;
&lt;p&gt;Production access until there is an explicit process usually comes down to who can deploy and needs to do it quickly. That feels like efficiency on a normal day. In a risky change it gets murky: was this checked? this the correct artifact? who is responsible for the go decision?&lt;/p&gt;
&lt;p&gt;If you’ve got repo access with deploy permissions, merging code is often synonymous with deploying it. That’s not really a decision, that’s just the way things happen when CI is doing the deployments.&lt;/p&gt;
&lt;p&gt;From what I can tell this only happens when something has gone wrong and suddenly no one knows exactly who owns the release. &lt;/p&gt;
&lt;h3 id=&quot;4-your-deployment-metrics-are-good-because-you-re-measuring-the-wrong-thing&quot;&gt;4. Your deployment metrics are good because you’re measuring the wrong thing&lt;/h3&gt;
&lt;p&gt;Pass/fail is the wrong indicator here. A release that makes it through on the third try after two reruns is not the same as one that went through cleanly the first time.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;rerun ratio&lt;/strong&gt; is the most overlooked stat by most teams. Successful releases with high rerun rates are symptoms of brittle steps, flaky dependencies, and &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.browserstack.com/guide/ci-cd-challenges-and-solutions?ref=hackernoon.com&quot;&gt;environment inconsistencies&lt;/a&gt; (even when the terminal status is green). One of the most underdiagnosed CI/CD challenges is environment inconsistency, as it doesn’t show up in pass/fail rates until a regression hits production.&lt;/p&gt;
&lt;p&gt;If your deployment telemetry does not distinguish between first-run success and ultimate success, you are missing a reliability signal that is important.&lt;/p&gt;
&lt;h3 id=&quot;5-staging-and-production-run-different-artifacts&quot;&gt;5. Staging and production run different artifacts&lt;/h3&gt;
&lt;p&gt;If you rebuild per environment instead of promoting the same artifact, there’s no guarantee that what passed QA is what ends up in production. Dependencies can resolve differently, configs can drift, and suddenly you’re debugging a bug you’ve never seen in staging.&lt;/p&gt;
&lt;p&gt;The fix is simple in theory: build once, promote the same artifact everywhere. But many CI setups rebuild by default, because that’s how the pipeline is wired.&lt;/p&gt;
&lt;p&gt;Most of the time, no one notices until a regression makes it obvious.&lt;/p&gt;
&lt;h3 id=&quot;6-a-ci-platform-outage-also-freezes-your-ability-to-deploy-a-hotfix&quot;&gt;6. A CI platform outage also freezes your ability to deploy a hotfix&lt;/h3&gt;
&lt;p&gt;If deployments run through your CI, a platform issue can take out both builds and releases at the same time. The moment you most need to ship a fix is the moment you can’t.&lt;/p&gt;
&lt;p&gt;I can say that this isn’t rare. Looking at a set of failed or delayed releases, the root cause was often not bad code but a fragile setup: wrong variables in CI, missing approvals, and unclear artifact versions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When your release layer depends entirely on your build layer, their availability becomes the same.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;7-deployment-knowledge-concentrates-on-one-or-two-people&quot;&gt;7. Deployment knowledge concentrates on one or two people&lt;/h3&gt;
&lt;p&gt;What I mean is that the team's ability to ship depends on specific individuals being present and available.&lt;/p&gt;
&lt;p&gt;You’ll agree that this is the least dramatic sign. Yet I’m convinced it’s the most damaging long-term. Teams usually notice it when the &quot;deployment person&quot; takes a holiday and suddenly nobody is confident releasing anything. Or when someone leaves and takes the full process knowledge with them.&lt;/p&gt;
&lt;p&gt;Deployment governance isn't just an operational concern. It's a scaling and team health concern.&lt;/p&gt;
&lt;h3 id=&quot;why-do-these-signs-appear-together-&quot;&gt;Why do these signs appear together?&lt;/h3&gt;
&lt;p&gt;None of these is a crisis on its own. That’s why they tend to stick around for so long.&lt;/p&gt;
&lt;p&gt;Together, they point to a release process optimized for the easy case: normal days, routine deploys, no incidents. The root cause is the same across all seven. &lt;strong&gt;CI and CD are different responsibilities.&lt;/strong&gt; One is about validating code, the other is about governing how it moves. But most setups try to handle both with a single system designed for only one of them.&lt;/p&gt;
&lt;p&gt;If you've gone deeper on the &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/15804/ci-and-cd-are-not-the-same-thing-how-to-build-a-resilient-release-process?ref=hackernoon.com&quot;&gt;CI vs. CD distinction&lt;/a&gt;, the pattern becomes clear quickly. The coupling feels efficient early and becomes fragile at scale. Build maintainers end up carrying production risk decisions they don't own. Deployment logic accumulates inside pipelines built for something else.&lt;/p&gt;
&lt;p&gt;As one measurement framework puts it: standardization doesn't remove complexity. &lt;strong&gt;It makes complexity measurable&lt;/strong&gt;. And once it's so, you can improve it deliberately instead of reacting to isolated incidents.&lt;/p&gt;
&lt;h3 id=&quot;how-many-did-you-recognize-&quot;&gt;How many did you recognize?&lt;/h3&gt;
&lt;p&gt;At first, you’ll most probably notice one or two. That’s nothing unusual; every team goes through this before major issues show up. But when you start seeing four or five, here’s where cracks are about to show. Things still run, but it’s clear the process is bogged down and getting heavier than it should be. Hit six or seven, and it’s trouble. The next incident is probably going to be costly for your team. Not just from any bug, but because all that extra process is slowing everything to a crawl. &lt;strong&gt;The faster you notice this pattern, the better.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-to-do-about-it&quot;&gt;What to do about it&lt;/h3&gt;
&lt;p&gt;The good news is that you don’t have to dismantle your pipeline to fix any of this.&lt;/p&gt;
&lt;p&gt;The main change here is &lt;strong&gt;splitting up the purpose of CI and the purpose of releasing&lt;/strong&gt;. CI’s whole job is to build, test, and create an artifact. And it does that just fine. Everything after that (what version goes where, who approved it, and what happens if it fails) is &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@techface/stop-using-ci-as-your-deployment-platform-427a1550ccfa?ref=hackernoon.com&quot;&gt;a whole other responsibility&lt;/a&gt; that needs its own structure.&lt;/p&gt;
&lt;p&gt;So, when you put this in practice, you end up with four key things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;build the artifact once, then push it to each environment instead of rebuilding at every stage&lt;/li&gt;
&lt;li&gt;make approvals a real, tracked step, not just a message in Slack&lt;/li&gt;
&lt;li&gt;test your rollback process before you face an actual incident&lt;/li&gt;
&lt;li&gt;keep your release history somewhere safe, so it doesn’t disappear on your CI failure&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You don't actually need a large platform. This thin &lt;a rel=&quot;nofollow&quot; href=&quot;https://orbnetes.cloud/try&quot;&gt;layer between build and release&lt;/a&gt;, which owns the release decision, captures the audit trail and handles rollback as policy rather than improvisation, removing most of the ambiguity without much overhead.&lt;/p&gt;
&lt;p&gt;I’ve seen this pattern enough times to know the structure problem only reveals itself under pressure. Everything is fine until it isn't. And when something breaks at a bad time, the question is not whether you can fix the bug, but whether you can understand what happened quickly enough to do something about it.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17388/7-signs-your-release-process-is-more-fragile-than-you-think</guid>
<pubDate>Mon, 11 May 2026 06:37:01 +0000</pubDate>
</item>
<item>
<title>Beyond CI/CD: Introducing Semantic Validation in DevOps Pipelines</title>
<link>https://coderlegion.com/16775/beyond-ci-cd-introducing-semantic-validation-in-devops-pipelines</link>
<description>&lt;p&gt;Most DevOps conversations focus on one question:&lt;/p&gt;
&lt;p&gt;Should CI and CD be separated?&lt;/p&gt;
&lt;p&gt;That matters—but I think it misses the deeper issue.&lt;/p&gt;
&lt;p&gt;Modern pipelines are good at answering:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can this code build?&lt;/li&gt;
&lt;li&gt;Do the tests pass?&lt;/li&gt;
&lt;li&gt;Can it be deployed?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But they rarely answer a more important question:&lt;/p&gt;
&lt;p&gt; Should this system state exist at all?&lt;/p&gt;
&lt;p&gt;This is where many pipelines fail quietly.&lt;/p&gt;
&lt;p&gt;A deployment can be technically successful while still introducing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;semantic inconsistencies&lt;/li&gt;
&lt;li&gt;invalid state transitions&lt;/li&gt;
&lt;li&gt;ambiguous rollback paths&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In other words:&lt;/p&gt;
&lt;p&gt;«Passing tests does not guarantee system truth.»&lt;/p&gt;
&lt;p&gt;So I’ve been exploring a different architecture:&lt;/p&gt;
&lt;p&gt;CI → Semantic Validation → CD&lt;/p&gt;
&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CI handles build and test&lt;/li&gt;
&lt;li&gt;Semantic Validation enforces meaning and state integrity&lt;ul&gt;
&lt;li&gt;SKOS for controlled semantics&lt;/li&gt;
&lt;li&gt;RDF for graph projection&lt;/li&gt;
&lt;li&gt;SPARQL for rule enforcement&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CD promotes only validated system states&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This changes deployment from:&lt;/p&gt;
&lt;p&gt;shipping code → promoting validated reality&lt;/p&gt;
&lt;p&gt;Benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clear state traceability&lt;/li&gt;
&lt;li&gt;Deterministic rollback&lt;/li&gt;
&lt;li&gt;Stronger governance over production changes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rollback becomes simple:&lt;/p&gt;
&lt;p&gt; Replay a known validated state.&lt;/p&gt;
&lt;p&gt;Not rebuild-and-hope.&lt;/p&gt;
&lt;p&gt;I believe the next evolution of DevOps is not just faster pipelines.&lt;/p&gt;
&lt;p&gt;It’s pipelines that can formally answer:&lt;/p&gt;
&lt;p&gt;What is allowed to exist in production?&lt;/p&gt;
&lt;p&gt;Curious how others think about this.&lt;/p&gt;
&lt;p&gt;Do your current pipelines validate only code, or system meaning too?&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/16775/beyond-ci-cd-introducing-semantic-validation-in-devops-pipelines</guid>
<pubDate>Tue, 05 May 2026 17:11:12 +0000</pubDate>
</item>
<item>
<title>Do you separate CI and CD?</title>
<link>https://coderlegion.com/16755/do-you-separate-ci-and-cd</link>
<description>&lt;p&gt;In a lot of setups I’ve seen, CI is responsible for everything—build, test, deploy, and even rollback. &lt;/p&gt;
&lt;p&gt;I'm convinced that this approach is not that efficient. Even the most basic things become harder than they should be. It’s no longer immediately clear what’s actually running in production, who approved the release, or what the last stable version was. And when something goes wrong, rolling back quickly without rebuilding turns into a stressful, manual process.&lt;/p&gt;
&lt;p&gt;The information is scattered across pipeline logs, commits, and chats.&lt;/p&gt;
&lt;p&gt;So I'm curious how you handle this. Do you separate CI and CD? If yes, what tools do you use? Or do you keep everything inside pipelines? &lt;/p&gt;
&lt;p&gt;Let's discuss!&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/16755/do-you-separate-ci-and-cd</guid>
<pubDate>Tue, 05 May 2026 12:38:19 +0000</pubDate>
</item>
<item>
<title>Octopus Easy Mode: Safely Creating Projects With the AI Assistants</title>
<link>https://coderlegion.com/16744/octopus-easy-mode-safely-creating-projects-with-the-ai-assistants</link>
<description>&lt;p&gt;AI in DevOps faces an interesting problem.&lt;/p&gt;
&lt;p&gt;According to &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/news/ai-pulse-report&quot;&gt;Octopus Deploy's AI Pulse report&lt;/a&gt;, 44% of developers say they're frustrated with AI outputs that are &quot;almost right, but not quite.&quot; &lt;/p&gt;
&lt;p&gt;You’ve likely experienced it yourself: a large language model generates a deployment config missing a critical variable, or suggests a step that works in staging but breaks in production. Which is why many engineers will use AI to whip up a script. Fewer will let it configure a production pipeline unsupervised.&lt;/p&gt;
&lt;p&gt;So far in the Easy Mode series, we’ve walked through how to use the Octopus AI Assistant for specific, bounded tasks, from adding manual interventions to setting environment-scoped variables.&lt;/p&gt;
&lt;p&gt;Each post focused on a task where you could verify the result before it hit production. This post continues that pattern. We’ll cover how to create projects and what to do if you need to rollback a change you are not satisfied with.&lt;/p&gt;
&lt;h3 id=&quot;what-the-ai-assistant-does-and-what-we-re-building-&quot;&gt;&lt;strong&gt;What the AI Assistant Does (And What We're Building)&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;If this is your first easy mode article, it is worth explaining what the Octopus AI assistant is and exactly what we will be building.&lt;/p&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://chromewebstore.google.com/detail/octopus-ai-assistant/agfpjjibnieiihjoehophlbamcifdfha?pli=1&quot;&gt;Octopus AI assistant&lt;/a&gt; is a Chrome extension that integrates AI directly into Octopus Deploy. It can answer questions about your instance, help you build projects, and guide you through configuration, all without leaving the browser tab you are already in.&lt;/p&gt;
&lt;p&gt;A common question users often have is “Does Octopus use my data for training?” to get that out of the way, the extension does NOT train AI models on your data.  &lt;/p&gt;
&lt;h3 id=&quot;demo-creating-a-project-with-the-ai-assistant&quot;&gt;&lt;strong&gt;Demo: Creating a Project With the AI Assistant&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In this walkthrough, we will create a &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/docs/projects&quot;&gt;project&lt;/a&gt; using the Octopus AI assistant and simulate responding to a request to revert a change. &lt;/p&gt;
&lt;h4 id=&quot;prerequisites-2&quot;&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;In order to follow along with this portion of the tutorial, you will need the following: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;An Octopus Cloud account:&lt;/strong&gt; If you don’t have one, you can sign up for a free trial.  &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Octopus AI Assistant Chrome extension:&lt;/strong&gt; You can install it from the &lt;a rel=&quot;nofollow&quot; href=&quot;https://chromewebstore.google.com/detail/octopus-ai-assistant/agfpjjibnieiihjoehophlbamcifdfha&quot;&gt;Chrome Web Store.&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Upon installation, you should see a new icon in the bottom right.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-403756714ca4404a842c1b7a7332da40.r2.dev/chromextension.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure 1:&lt;/strong&gt; &lt;em&gt;Visual of the Octopus AI Assistant after Chrome extension installation.&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;create-a-project&quot;&gt;&lt;strong&gt;Create a project&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;With the extension installed, the next step is to create a project. &lt;/p&gt;
&lt;p&gt;Paste the following prompt into the Octopus AI Assistant and run it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Create a Script project called &quot; Git backed Project&quot;.  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-403756714ca4404a842c1b7a7332da40.r2.dev/prompt.gif&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure 2:&lt;/strong&gt; &lt;em&gt;Visual of creating and executing a project in Octopus AI assistant&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Upon hitting enter, the Octopus AI assistant will process your request and generate the OpenTofu code required. This is important because using OpenTofu makes your results much more deterministic. &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=13054018376292372835&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure 3:&lt;/strong&gt; &lt;em&gt;Generating the OpenToFu&lt;/em&gt; &lt;/p&gt;
&lt;p&gt;Many engineers have expressed doubts when using generative AI, as the results tend to vary. Generating the OpenTofu required to fulfill your requests helps the assistant achieve greater accuracy, as there is only one way to create a project via the OpenTofu provider. &lt;/p&gt;
&lt;p&gt;Additionally, if you were to do this manually, it would take anywhere from a couple of minutes to a few minutes, depending on how many projects you intend to create and whether you need to modify an existing OpenTofu module. &lt;/p&gt;
&lt;h3 id=&quot;your-safety-net-version-controlled-projects&quot;&gt;&lt;strong&gt;Your Safety Net: Version-Controlled Projects&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;So far, the Octopus AI assistant has created your project. The obvious question is: what if something is wrong?&lt;/p&gt;
&lt;p&gt;Thankfully, Octopus Deploy lets you back any project with a Git repository. When you enable this, every change to that project, whether you made it or the assistant did, becomes a Git commit.&lt;/p&gt;
&lt;p&gt;To set this up, go to your project settings and connect a Git repository. Once connected, Octopus stores your project configuration in a &lt;code&gt;.octopus&lt;/code&gt; folder in that repo. Deployment processes, variables, triggers, all of it lives in version-controlled files.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://pub-403756714ca4404a842c1b7a7332da40.r2.dev/version-control.gif&quot; alt=&quot;vcs&quot;&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Figure 4:&lt;/strong&gt; &lt;em&gt;Visual of version control being enabled in project settings&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now look at what happens after the AI assistant creates your project. Open the Git history for the repo. You'll see the commits the assistant generated, and you can inspect exactly what it configured.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=14130679584198245489&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;strong&gt;Figure 5:&lt;/strong&gt; &lt;em&gt;Visual image of the committed files&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Clicking into any commit gives you the full diff, and if something looks off, you have two options: fix it in the next commit or revert to a known-good state. This is the same workflow you already use for application code.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=6036324195504826389&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;strong&gt;Figure 6:&lt;/strong&gt; &lt;em&gt;Visual image of the diffs&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This matters because you can use the assistant with more confidence, because changes are auditable, and because relying on a config-as-code approach means there is only one way to do things. &lt;/p&gt;
&lt;h3 id=&quot;tips-for-getting-the-most-out-of-ai-assisted-setup&quot;&gt;Tips for Getting the Most Out of AI-Assisted Setup&lt;/h3&gt;
&lt;p&gt;At this stage, if you’re looking to try out an AI-Assisted workflow, here are some quick tips on how to get better returns on your new workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Write short, specific prompts:&lt;/strong&gt; The more detail you pack into a single prompt, the longer the assistant takes to process it. Keep prompts scoped to a single task.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Include platform and environment details.&lt;/strong&gt; The assistant produces better results when you tell it what you're deploying to. Specify the target (Kubernetes, Azure App Service, AWS ECS), name your environments, and indicate your preferred deployment strategy (rolling, blue/green, canary) if applicable.  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan to customize after generation:&lt;/strong&gt; The assistant handles straightforward project scaffolding well. Complex variable scoping, multi-tenant configurations, and custom scripts are better handled manually after the initial setup. Use the AI to get the structure in place, then fine-tune. Git tracks everything either way.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Beyond project creation, the AI Assistant can also help with troubleshooting failed deployments and surfacing best-practice recommendations.&lt;/p&gt;
&lt;h3 id=&quot;build-fast-ship-safe&quot;&gt;Build Fast, Ship Safe&lt;/h3&gt;
&lt;p&gt;In this post, we used the Octopus AI Assistant to create a project from a prompt and then backed it with Git via Config as Code.&lt;/p&gt;
&lt;p&gt;While the conversation around AI in DevOps evolves, having the ability to use it on your own terms is what makes the Octopus AI assistant a good choice for teams looking to dip their toes in the water.&lt;/p&gt;
&lt;p&gt;If you want to try this yourself, &lt;a rel=&quot;nofollow&quot; href=&quot;https://chromewebstore.google.com/detail/octopus-ai-assistant/agfpjjibnieiihjoehophlbamcifdfha&quot;&gt;install the Octopus AI Assistant Chrome extension&lt;/a&gt; and give it a shot.&lt;/p&gt;
&lt;p&gt;Happy deployments!&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/16744/octopus-easy-mode-safely-creating-projects-with-the-ai-assistants</guid>
<pubDate>Tue, 05 May 2026 10:16:56 +0000</pubDate>
</item>
<item>
<title>A Soft Introduction to EasyShell</title>
<link>https://coderlegion.com/16702/a-soft-introduction-to-easyshell</link>
<description>&lt;blockquote&gt;&lt;p&gt;&quot;Can it be simpler?&quot; - A recurring thought I've had while working on build automation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MethodoxTech/EasyShell&quot;&gt;EasyShell&lt;/a&gt; (command: &lt;code&gt;easy&lt;/code&gt;) is a minimal shell scripting language designed from the ground up for &lt;strong&gt;simplicity and cross-platform build/process automation&lt;/strong&gt;. If you’ve ever felt that PowerShell’s syntax is powerful but easy to forget, or that traditional shell scripts get messy across operating systems, EasyShell aims to strike a cleaner balance.&lt;/p&gt;
&lt;p&gt;It's implemented in C# and runs on .NET, giving you natural access to the entire .NET ecosystem while keeping the scripting experience lightweight and approachable.&lt;/p&gt;
&lt;h3 id=&quot;design-principles&quot;&gt;Design Principles&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;One command per line (shell-like flow)&lt;/li&gt;
&lt;li&gt;Very small set of core concepts&lt;/li&gt;
&lt;li&gt;Strongly-typed global variables&lt;/li&gt;
&lt;li&gt;Seamless .NET interop via reflection&lt;/li&gt;
&lt;li&gt;Expressions in parentheses for inline evaluation&lt;/li&gt;
&lt;li&gt;Easy to read, write, and remember&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MethodoxTech/EasyShell/releases/tag/v0.1.0&quot;&gt;Version &lt;strong&gt;0.1.0&lt;/strong&gt;&lt;/a&gt; is now available — functional for everyday automation tasks.&lt;/p&gt;
&lt;h3 id=&quot;core-language-features&quot;&gt;Core Language Features&lt;/h3&gt;
&lt;h4 id=&quot;variables&quot;&gt;Variables&lt;/h4&gt;
&lt;p&gt;All variables are declared with an explicit type and live in global scope:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-easy&quot;&gt;INTVAR Count 42
BOOLVAR Debug TRUE
DOUBLEVAR Ratio 3.14
STRINGVAR Message &quot;Hello from EasyShell&quot;
HANDLEVAR Now System.DateTime.Now   # stores object instances
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Variable names are &lt;strong&gt;case-insensitive&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Reference with &lt;code&gt;$VarName&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Re-assign with &lt;code&gt;$VarName = value&lt;/code&gt; (or expressions)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;expressions-the-power-feature-&quot;&gt;Expressions (The Power Feature)&lt;/h4&gt;
&lt;p&gt;Wrap sub-commands in parentheses to evaluate them inline:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-easy&quot;&gt;STRINGVAR Formatted (System.String.Format &quot;Count = {0}&quot; $Count)
BOOLVAR IsBig (&amp;gt; $Count 100)
STRINGVAR Today (format &quot;{0:yyyy-MM-dd}&quot; (GetDate))
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;calling-net&quot;&gt;Calling .NET&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Static members&lt;/strong&gt; (fully qualified):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-easy&quot;&gt;System.Console.WriteLine &quot;Hello World!&quot;
System.IO.File.WriteAllText &quot;output.txt&quot; $Message
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Instance methods&lt;/strong&gt; via HANDLE:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-easy&quot;&gt;HANDLEVAR Now System.DateTime.Now
STRINGVAR Stamp (CALL $Now ToString)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;control-flow&quot;&gt;Control Flow&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-easy&quot;&gt;IF (== $Count 42)
    print &quot;The answer!&quot;
ELSEIF (&amp;gt; $Count 50)
    print &quot;Too high&quot;
ELSE
    print &quot;Something else&quot;
END

INTVAR I 0
WHILE (&amp;lt; $I 5)
    System.Console.WriteLine $I
    $I = (+ $I 1)   # arithmetic helpers coming soon
END
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;functions&quot;&gt;Functions&lt;/h4&gt;
&lt;p&gt;Simple named blocks (no parameters — use globals):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-easy&quot;&gt;FUNC BuildProject
    print &quot;Building...&quot;
    dotnet build
END

CALL BuildProject
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;external-commands&quot;&gt;External Commands&lt;/h4&gt;
&lt;p&gt;Any command that isn’t a keyword or .NET call falls back to executing an external process (with stdout capture):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-easy&quot;&gt;git status
dotnet publish ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;real-world-example&quot;&gt;Real-World Example&lt;/h3&gt;
&lt;p&gt;Check out the full build script used by the project itself:&lt;br&gt;
&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MethodoxTech/EasyShell/blob/main/BuildScripts/BuildEasyShell.easy&quot;&gt;BuildEasyShell.easy&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It demonstrates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Path handling and folder creation&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;dotnet publish&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Conditional logic for OS/architecture&lt;/li&gt;
&lt;li&gt;Creating a dated zip package&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This script is self-contained and shows how EasyShell can manage non-trivial automation tasks.&lt;/p&gt;
&lt;h3 id=&quot;quick-start&quot;&gt;Quick Start&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# REPL mode
easy

# Run a script
easy path/to/script.easy

# Help
easy --help
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A dedicated &lt;strong&gt;VS Code syntax highlighting extension&lt;/strong&gt; is available: &lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=Methodox.easyshell&quot;&gt;Methodox.easyshell&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;who-is-it-for-&quot;&gt;Who Is It For?&lt;/h3&gt;
&lt;p&gt;EasyShell is ideal for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Build automation scripts&lt;/li&gt;
&lt;li&gt;Simple CI/CD glue&lt;/li&gt;
&lt;li&gt;Daily developer tooling&lt;/li&gt;
&lt;li&gt;Anyone who wants .NET power without heavy dependency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's intentionally minimal — no advanced Bash-style features, just the essentials done in a way that’s hard to forget.&lt;/p&gt;
&lt;p&gt;Give it a try and let me know what you think!&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/16702/a-soft-introduction-to-easyshell</guid>
<pubDate>Tue, 05 May 2026 02:31:45 +0000</pubDate>
</item>
<item>
<title>DevLog 20251214: Yet Another Shell Script – Keeping Build Automation Simple with EasyShell</title>
<link>https://coderlegion.com/16701/devlog-20251214-yet-another-shell-script-keeping-build-automation-simple-with-easyshell</link>
<description>&lt;p&gt;As we roll out more tools, both internal and public, there has been a growing need for easy-to-maintain shell scripting and build automation. Build automation is complex enough without all the syntax burden - my favorite is to use PowerShell, but often it feels like wrestling with the tool itself. PowerShell is powerful, sure, but its depth often means hunting down syntax I &lt;em&gt;know&lt;/em&gt; I've used a dozen times before. Bash has its quirks across platforms, and honestly I don't know good ways of using it outside MinGW, Git Bash, or WSL - none of which felt quite &quot;right&quot;.&lt;/p&gt;
&lt;p&gt;I wanted something lighter: a shell scripting tool that's &lt;strong&gt;easy to pick up, cross-platform, and hard to forget&lt;/strong&gt; once you start using it.&lt;/p&gt;
&lt;p&gt;So I made &lt;strong&gt;EasyShell&lt;/strong&gt; (or just &lt;code&gt;easy&lt;/code&gt;): a minimal shell scripting language implemented in C# that focuses on process automation with a very small set of concepts.&lt;/p&gt;
&lt;h3 id=&quot;motivation-and-design&quot;&gt;Motivation and Design&lt;/h3&gt;
&lt;p&gt;EasyShell is for &quot;lazy&quot; developers (like me) who just want to automate common tasks - calling .NET APIs, running external tools, handling simple logic - without a steep syntax curve or platform-specific headaches. It combines a shell-like one-command-per-line flow with strong typing and easy .NET interop. No advanced Bash-style wizardry; just straightforward scripting.&lt;/p&gt;
&lt;p&gt;It could also serve as a nice companion alongside other tools in the long-term Divooka ecosystem.&lt;/p&gt;
&lt;h3 id=&quot;quick-taste-of-the-syntax&quot;&gt;Quick Taste of the Syntax&lt;/h3&gt;
&lt;p&gt;Files typically use the &lt;code&gt;.easy&lt;/code&gt; extension, with syntax highlighting available via the &lt;a rel=&quot;nofollow&quot; href=&quot;https://marketplace.visualstudio.com/items?itemName=Methodox.easyshell&quot;&gt;VS Code extension&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here's a small sample from the repo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-easy&quot;&gt;# Comments start with #
HANDLEVAR NOW System.DateTime.Now

# Instance method via CALL
STRINGVAR DATE (CALL $NOW ToString)

# Static .NET call with expression
STRINGVAR VALUE (System.String.Format &quot;Current time: {0}&quot; $DATE)

STRINGVAR PATH &quot;C:/temp/greeting.txt&quot;
System.IO.File.WriteAllText $PATH $VALUE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Variables are declared with their type (&lt;code&gt;INTVAR&lt;/code&gt;, &lt;code&gt;STRINGVAR&lt;/code&gt;, &lt;code&gt;BOOLVAR&lt;/code&gt;, &lt;code&gt;DOUBLEVAR&lt;/code&gt;, &lt;code&gt;HANDLEVAR&lt;/code&gt;), names are case-insensitive, and everything lives in global scope for simplicity. Expressions are wrapped in parentheses for inline evaluation, like &lt;code&gt;(== $X 10)&lt;/code&gt; or sub-commands.&lt;/p&gt;
&lt;p&gt;Control flow sticks to the basics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;IF ... ELSEIF ... ELSE ... END&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WHILE ... END&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Simple &lt;code&gt;FUNC ... END&lt;/code&gt; / &lt;code&gt;CALL&lt;/code&gt; (globals make &quot;returns&quot; easy via variables)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can call external executables naturally, and non-built-in commands fall through to process execution (stdout capture included).&lt;/p&gt;
&lt;h3 id=&quot;early-days&quot;&gt;Early Days&lt;/h3&gt;
&lt;p&gt;This is version 0.1.0 territory - functional for everyday build tasks, with room to grow (arithmetic helpers, more examples, etc.). You can run it in REPL mode (&lt;code&gt;easy&lt;/code&gt;) or point it at a script. Builds and packages are straightforward with the included scripts.&lt;/p&gt;
&lt;p&gt;If you've ever wished for something between a full PowerShell script and a quick batch file that &quot;just works&quot; across machines, maybe give EasyShell a try. The GitHub repo has the full language overview, examples, and build details: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/MethodoxTech/EasyShell&quot;&gt;MethodoxTech/EasyShell&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16701/devlog-20251214-yet-another-shell-script-keeping-build-automation-simple-with-easyshell</guid>
<pubDate>Tue, 05 May 2026 02:23:48 +0000</pubDate>
</item>
<item>
<title>Orbnetes — Release Orchestration</title>
<link>https://coderlegion.com/16228/orbnetes-release-orchestration</link>
<description>&lt;p&gt;I kept running into the same problem. CI tools are great at building code, but not at releasing it. Approvals feel bolted on, rollback is improvised, and when something breaks, nobody can answer who deployed what or when.&lt;/p&gt;
&lt;p&gt;So I built &lt;strong&gt;Orbnetes&lt;/strong&gt; — a self-hosted release orchestration layer that sits on top of your existing CI and handles everything after the build finishes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You connect it to GitHub, GitLab, or your own storage. It picks up the artifact, validates it, and manages the release from there — approvals, environment promotion, rollback, full audit record.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=908888245211829804&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Deployments run through lightweight cross-platform agents (~2MB) inside your own infrastructure. Deployment logic lives in YAML blueprints with a syntax close to GitHub Actions — familiar but scoped purely to release control.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=1762107946470585175&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Every release gets a structured record. If something fails, rollback executes against the already-validated artifact not a pipeline re-run.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=4557192326772151328&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=8750719592175599704&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key features&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cross-platform agents for Linux, macOS, Windows&lt;/li&gt;
&lt;li&gt;YAML blueprints with approval gates and rollback policies&lt;/li&gt;
&lt;li&gt;Full audit trail independent of your CI platform&lt;/li&gt;
&lt;li&gt;Self-hosted, free, no Kubernetes required&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://orbnetes.cloud/&quot;&gt;Orbnetes&lt;/a&gt; is free to try. Would love to hear your feedback!&lt;/p&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/16228/orbnetes-release-orchestration</guid>
<pubDate>Thu, 30 Apr 2026 06:45:23 +0000</pubDate>
</item>
<item>
<title>CI and CD Are Not the Same Thing: How to Build a Resilient Release Process</title>
<link>https://coderlegion.com/15804/ci-and-cd-are-not-the-same-thing-how-to-build-a-resilient-release-process</link>
<description>&lt;p&gt;If you’re building a software product, your deployment process matters from day one. It’s not a “later” problem or just DevOps work in the background. It directly impacts how stable your product is, how fast you recover from issues, and how confident your team feels when shipping changes.&lt;/p&gt;
&lt;p&gt;Most teams know this. But in practice, their release process looks very different.&lt;/p&gt;
&lt;p&gt;Instead of a clear system, they end up with a patchwork of CI jobs — a deploy step here, a Slack alert there, a quick workaround that quietly becomes permanent.&lt;/p&gt;
&lt;p&gt;It works. Until it doesn’t.&lt;/p&gt;
&lt;p&gt;And when something breaks, the same question comes up: what just happened?&lt;/p&gt;
&lt;p&gt;If that sounds familiar, this article is for you.&lt;/p&gt;
&lt;h3 id=&quot;ci-and-cd-are-not-the-same-thing&quot;&gt;CI and CD are not the same thing&lt;/h3&gt;
&lt;p&gt;I’ve been building tech products for over ten years now, managing development teams through the full release cycle, from early-stage pipelines to production systems under real operational pressure. That experience made the gap between CI and CD for me impossible to ignore.&lt;/p&gt;
&lt;p&gt;Usually, teams treat CI platforms as the &lt;strong&gt;default place to do everything&lt;/strong&gt;: build, test, package, deploy, promote, and sometimes even roll back. At first it feels efficient. Your source code is already in GitHub or GitLab, your pipeline definitions live next to your code, your runners are already there. So why not push deployment and release orchestration into the same system too?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Because convenience at the beginning often becomes coupling at scale.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CI and CD are related, but they are not the same responsibility. CI is about validating change – build the code, run the tests, produce an artifact, confirm the commit is healthy. It answers one question: &lt;strong&gt;&quot;Is this change valid?&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CD is about controlled change propagation – selecting a release candidate, deciding where it goes, defining an approval flow, promoting through environments, enforcing role-based access, capturing audit history, handling rollback safely. It answers a completely different question: &lt;strong&gt;&quot;Who deployed which version, where, when, and what happened next?&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Those are different questions and they clearly deserve different systems.&lt;/p&gt;
&lt;h3 id=&quot;the-hidden-cost-of-using-ci-for-releases&quot;&gt;The hidden cost of using CI for releases&lt;/h3&gt;
&lt;p&gt;Using GitHub Actions or GitLab pipelines for deployments is not wrong. It is often the fastest way to get started. But once deployment logic lives inside CI, several problems pile up.&lt;/p&gt;
&lt;h3 id=&quot;1-your-release-process-becomes-coupled-to-your-source-platform&quot;&gt;1. Your release process becomes coupled to your source platform&lt;/h3&gt;
&lt;p&gt;If your CI platform is degraded, your release process is too. GitHub's incident history shows multiple events where Actions jobs failed or were delayed (sometimes in the same window that affected pull requests and API requests).&lt;/p&gt;
&lt;p&gt;When your builds, approvals, and production releases all depend on the same platform, one outage freezes both development and delivery simultaneously. If you need to ship a hotfix during that window, &quot;everything in one place&quot; stops looking like an advantage, it becomes a single operational dependency.&lt;/p&gt;
&lt;h3 id=&quot;2-ci-pipelines-are-great-at-execution-but-awkward-at-release-governance&quot;&gt;2. CI pipelines are great at execution but awkward at release governance&lt;/h3&gt;
&lt;p&gt;Pipelines are good at running jobs. They are less natural as systems of record for release intent. Yes, you can build approvals into workflows. Yes, you can model environment promotion in YAML. Yes, you can encode rollback logic in scripts. But over time teams start asking questions that CI was never built to answer cleanly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which exact version was approved for staging?&lt;/li&gt;
&lt;li&gt;Who approved the promotion to production, and when?&lt;/li&gt;
&lt;li&gt;Was QA sign-off captured before the release went out?&lt;/li&gt;
&lt;li&gt;What was the previous successful version?&lt;/li&gt;
&lt;li&gt;Can we roll back automatically if a deployment health check fails?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At that point deployment stops being &quot;just another job&quot; and becomes an operational workflow, one the pipeline was never designed to own.&lt;/p&gt;
&lt;h3 id=&quot;3-release-logic-becomes-too-tightly-bound-to-repository-events&quot;&gt;3. Release logic becomes too tightly bound to repository events&lt;/h3&gt;
&lt;p&gt;Most CI-driven deployment patterns are built around repository triggers: push to branch, merge to main, create a tag. That works when your release process is simple. But at some point teams start wanting things pipelines were never designed for — build once, deploy many times; promote the same artifact from staging to production without rebuilding it; let operations trigger a deployment without touching the codebase; roll back without re-running anything. The pipeline starts fighting back. That friction is usually the first sign that deployment has outgrown CI.&lt;/p&gt;
&lt;h3 id=&quot;what-this-costs-at-2am&quot;&gt;What this costs at 2am&lt;/h3&gt;
&lt;p&gt;The real cost becomes visible during a production incident when every minute matters. These are the questions that come up in the first ten minutes:&lt;/p&gt;
&lt;p&gt;Who deployed the version currently running in production?&lt;br&gt;
Was QA sign-off captured before this version was promoted?&lt;br&gt;
What was the last known stable version before this deployment?&lt;br&gt;
Can we roll back in the next five minutes without re-running a build?&lt;br&gt;
When CI-driven deployments cannot answer these (because the information is scattered across pipeline logs, Slack threads, and individual memory) the incident takes longer to take care of.&lt;/p&gt;
&lt;p&gt;For teams building tech products, every minute of downtime has a direct user-facing impact. And there is a less visible cost too: the team gets slower without understanding why.&lt;/p&gt;
&lt;p&gt;Rollback deserves particular attention here. In many CI-driven setups, rollback isn’t a defined operation. It’s a scramble:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;find the old tag&lt;/li&gt;
&lt;li&gt;re-run a pipeline&lt;/li&gt;
&lt;li&gt;hope nothing has drifted&lt;/li&gt;
&lt;li&gt;reconstruct whatever parameters were used last time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That’s improvisation under pressure. A release system should treat rollback as a first-class capability – &lt;strong&gt;tested, predictable, and fast.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because at 2am, clarity matters more than anything else.&lt;/p&gt;
&lt;h3 id=&quot;keep-ci-and-cd-connected-not-fused&quot;&gt;Keep CI and CD connected, not fused&lt;/h3&gt;
&lt;p&gt;The fix is not to replace your CI setup. GitHub Actions is powerful, GitLab CI is well-designed, and Jenkins is still deeply useful in many organizations. This is an architectural argument, not a tooling one.&lt;/p&gt;
&lt;p&gt;Keep CI where it belongs – &lt;strong&gt;building, testing, packaging, publishing a versioned artifact.&lt;/strong&gt; Let it answer &lt;strong&gt;&quot;is this change ready?&quot;&lt;/strong&gt; and stop there. Then hand off to a separate, independent release layer that owns everything that happens next: which version ships, to which environment, with whose approval, under which rollback policy, with a full audit record that survives independently of your source platform.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=1754226023092024921&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;That separation gives you something most teams are missing: &lt;strong&gt;a controlled release process instead of an automated shell script with side effects.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The best release process is not the one with the fewest steps, it is the one that still makes sense at 2am during a production incident.&lt;/p&gt;
&lt;h3 id=&quot;how-to-actually-separate-ci-from-cd-and-what-to-use-&quot;&gt;How to actually separate CI from CD (and what to use)&lt;/h3&gt;
&lt;p&gt;If your infrastructure is already mature, there are several established paths.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Spinnaker&lt;/strong&gt; is a powerful open-source CD platform with strong support for multi-cloud deployments and promotion workflows but it comes with significant operational overhead.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Argo CD&lt;/strong&gt; is a solid choice for Kubernetes-native teams. It follows a GitOps model and has strong community support, but it is tightly coupled to Kubernetes environments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flux&lt;/strong&gt; takes a similar GitOps approach with a lighter footprint, though it shares the same Kubernetes focus.&lt;/p&gt;
&lt;p&gt;And tools like &lt;strong&gt;GitLab&lt;/strong&gt; can handle both CI and deployment in a single platform, something that works well for many teams, especially early on.&lt;/p&gt;
&lt;p&gt;But these tools solve different slices of the problem. They also assume a certain level of infrastructure maturity. For many teams, they introduce as much complexity as they remove.&lt;/p&gt;
&lt;p&gt;But the key idea is super simple: &lt;strong&gt;separate the system that builds artifacts from the one that promotes and deploys them.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That became very obvious to me when I started testing deployment workflows outside of a single-machine setup – running services across isolated environments, with separate infrastructure components, and real failure conditions. The moment you leave the comfort of a local setup, hidden assumptions in pipeline-based deployments start to surface quickly.&lt;/p&gt;
&lt;p&gt;That’s the reason the idea of &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://orbnetes.cloud/&quot;&gt;Orbnetes&lt;/a&gt;&lt;/strong&gt; came into my mind. It’s a self-hosted release orchestration layer designed to sit on top of your existing CI, without requiring Kubernetes or heavy operational overhead.&lt;/p&gt;
&lt;p&gt;Instead of rebuilding per environment or encoding release logic in pipelines, the system focuses on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;promoting immutable artifacts&lt;/li&gt;
&lt;li&gt;defining explicit approval flows&lt;/li&gt;
&lt;li&gt;and keeping a release record independent of CI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal isn’t to replace your CI, but to give deployments their own control plane – one that still makes sense when things go wrong.&lt;/p&gt;
&lt;h3 id=&quot;how-orbnetes-works-in-practice&quot;&gt;How Orbnetes works in practice&lt;/h3&gt;
&lt;p&gt;Your CI pipeline stays exactly as it is. It builds, tests, and produces an artifact.&lt;/p&gt;
&lt;p&gt;Orbnetes picks up from that point. It integrates with systems like GitHub and GitLab, as well as internal artifact storage or webhooks, and turns build outputs into versioned releases.&lt;/p&gt;
&lt;p&gt;Each artifact is validated (for example via checksum) and stored as a release candidate. From that point on, deployments use the same immutable artifact across environments – no rebuilding, no hidden differences between staging and production.&lt;/p&gt;
&lt;p&gt;That one decision alone removes an entire class of deployment inconsistencies.&lt;/p&gt;
&lt;p&gt;Deployments are executed through lightweight agents running inside your infrastructure. They are cross-platform, isolated, and centrally managed, so environments stay independent, but the release process stays consistent.&lt;/p&gt;
&lt;p&gt;Instead of embedding deployment logic in CI pipelines, it lives in blueprints – YAML definitions designed for release orchestration rather than build automation.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: production-release
inputs:
  target_env:
    type: string
    required: true

jobs:
  backup:
    tags: [linux]
    steps:
      - name: pre-deploy-backup
        run: echo &quot;Backup before deploy&quot;

  deploy:
    needs: [backup]
    tags: [linux]
    steps:
      - name: deploy-release
        run: echo &quot;Deploying artifact&quot;
  verify:
    needs: [deploy]
    tags: [linux]
    steps:
      - name: health-check
        run: echo &quot;Verifying deployment&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The syntax is intentionally familiar, but the responsibility is different: this is about releasing software, not building it.&lt;/p&gt;
&lt;p&gt;Blueprints are versioned, reusable, and visible to the whole team rather than hidden inside a pipeline file that only a few people understand.&lt;/p&gt;
&lt;p&gt;Before a release reaches a sensitive environment, it goes through explicit approval flows. Roles are separated: the people who build, approve, and deploy are not implicitly the same. Every action is recorded.&lt;/p&gt;
&lt;p&gt;If something fails, rollback is not a guess. The system restores the last known good version using the already validated artifact. It is defined upfront, not improvised during an incident.&lt;/p&gt;
&lt;p&gt;Every release becomes a tracked event:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;what version was deployed&lt;/li&gt;
&lt;li&gt;where it went&lt;/li&gt;
&lt;li&gt;who approved it&lt;/li&gt;
&lt;li&gt;what happened next&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And importantly, this record exists independently of your CI platform. If GitHub has an outage, your release history (and your ability to operate) does not disappear with it.&lt;/p&gt;
&lt;h3 id=&quot;don-t-wait-for-an-incident-to-fix-this&quot;&gt;Don't wait for an incident to fix this&lt;/h3&gt;
&lt;p&gt;CI tells you whether your software is ready. CD determines how it actually reaches production.&lt;/p&gt;
&lt;p&gt;Those are &lt;strong&gt;different responsibilities&lt;/strong&gt; and treating them as the same system is where teams start to lose control.&lt;/p&gt;
&lt;p&gt;At a small scale, pipeline-driven deployments feel efficient. Everything is in one place, automation is easy, and shipping is fast. But as systems grow, that convenience turns into coupling. And coupling turns into fragility (usually revealed at the worst possible moment).&lt;/p&gt;
&lt;p&gt;During an incident, the problem is never just the bug. It’s the lack of clarity:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What exactly is running in production?&lt;/li&gt;
&lt;li&gt;Who approved it?&lt;/li&gt;
&lt;li&gt;What changed?&lt;/li&gt;
&lt;li&gt;How fast can we safely roll back?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal is not to add more steps or slow teams down. It’s to make releases predictable, observable, and controlled even under pressure.&lt;/p&gt;
&lt;p&gt;That starts with a simple shift: &lt;strong&gt;build in CI, release through CD, keep them connected, but not fused.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If this resonates with how your team works, Orbnetes is free to try. The documentation covers full setup in under 30 minutes, and the &lt;a rel=&quot;nofollow&quot; href=&quot;https://orbnetes.cloud/blueprints&quot;&gt;blueprints&lt;/a&gt; library has ready-made examples for common deployment patterns across Linux, Docker, and multi-environment setups.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Remember that your deployment process is part of your product.&lt;/strong&gt; Treat it that way:)&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15804/ci-and-cd-are-not-the-same-thing-how-to-build-a-resilient-release-process</guid>
<pubDate>Sat, 25 Apr 2026 11:37:27 +0000</pubDate>
</item>
<item>
<title>Why CI/CD Changed Software Development Forever</title>
<link>https://coderlegion.com/15722/why-ci-cd-changed-software-development-forever</link>
<description>&lt;p&gt;There was a time when deploying software was scary.&lt;/p&gt;
&lt;p&gt;Really scary.&lt;/p&gt;
&lt;p&gt;Developers would write code for weeks…&lt;/p&gt;
&lt;p&gt;Then manually deploy everything at once and pray:&lt;/p&gt;
&lt;p&gt; “Hope nothing breaks in production.”&lt;/p&gt;
&lt;p&gt;Sometimes it worked.&lt;/p&gt;
&lt;p&gt;Sometimes…&lt;/p&gt;
&lt;p&gt;Production exploded &lt;/p&gt;
&lt;p&gt;That’s exactly the problem &lt;strong&gt;CI/CD&lt;/strong&gt; helped solve.&lt;/p&gt;
&lt;p&gt;Let’s break it down simply &lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;before-ci-cd-releases-were-painful&quot;&gt;Before CI/CD: Releases Were Painful&lt;/h3&gt;
&lt;p&gt;Earlier, shipping software often looked like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Developers wrote code for weeks&lt;/li&gt;
&lt;li&gt;Changes piled up&lt;/li&gt;
&lt;li&gt;Big release day arrived&lt;/li&gt;
&lt;li&gt;Team manually deployed everything&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And then:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unexpected bugs&lt;/li&gt;
&lt;li&gt;Broken builds&lt;/li&gt;
&lt;li&gt;Failed deployments&lt;/li&gt;
&lt;li&gt;Long downtime&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Releasing software felt risky.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-common-problems-before-ci-cd&quot;&gt;⚠️ Common Problems Before CI/CD&lt;/h3&gt;
&lt;p&gt;Without automation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Testing was often manual&lt;/li&gt;
&lt;li&gt;Bugs were caught late&lt;/li&gt;
&lt;li&gt;Deployments were error-prone&lt;/li&gt;
&lt;li&gt;Rollbacks were messy&lt;/li&gt;
&lt;li&gt;“Works on my machine” became a lifestyle&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sound familiar? &lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-is-ci-cd-&quot;&gt;What is CI/CD?&lt;/h2&gt;
&lt;p&gt;CI/CD stands for:&lt;/p&gt;
&lt;h4 id=&quot;-continuous-integration-ci-&quot;&gt;✅ Continuous Integration (CI)&lt;/h4&gt;
&lt;p&gt;Developers push code frequently.&lt;/p&gt;
&lt;p&gt;Every change automatically:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Builds the project&lt;/li&gt;
&lt;li&gt;Runs tests&lt;/li&gt;
&lt;li&gt;Checks if anything broke&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Problems are caught early.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;continuous-delivery-deployment-cd-&quot;&gt;Continuous Delivery / Deployment (CD)&lt;/h4&gt;
&lt;p&gt;After code passes checks:&lt;/p&gt;
&lt;p&gt;It can be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prepared for release automatically&lt;/li&gt;
&lt;li&gt;Or deployed automatically&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Shipping becomes faster and safer.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-happens-in-a-ci-cd-pipeline-&quot;&gt;What Happens in a CI/CD Pipeline?&lt;/h3&gt;
&lt;p&gt;Typical flow:&lt;/p&gt;
&lt;p&gt;Code Push → Build → Test → Security Checks → Deploy&lt;/p&gt;
&lt;p&gt;Simple idea.&lt;/p&gt;
&lt;p&gt;Powerful impact.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-changed-everything&quot;&gt;Why This Changed Everything&lt;/h3&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;p&gt;❌ Big risky releases&lt;/p&gt;
&lt;p&gt;Now:&lt;/p&gt;
&lt;p&gt;✅ Small frequent safe releases&lt;/p&gt;
&lt;p&gt;That changes software development completely.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-real-benefits-of-ci-cd&quot;&gt;⚡ Real Benefits of CI/CD&lt;/h3&gt;
&lt;h4 id=&quot;faster-development&quot;&gt;Faster Development&lt;/h4&gt;
&lt;p&gt;Developers ship features quicker.&lt;/p&gt;
&lt;p&gt;No waiting for huge release cycles.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;better-code-quality&quot;&gt;Better Code Quality&lt;/h4&gt;
&lt;p&gt;Automated tests catch issues early.&lt;/p&gt;
&lt;p&gt; Less broken code reaching users.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;safer-deployments&quot;&gt;Safer Deployments&lt;/h4&gt;
&lt;p&gt;Every deployment follows a repeatable process.&lt;/p&gt;
&lt;p&gt;No random manual mistakes.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;faster-bug-fixes&quot;&gt;Faster Bug Fixes&lt;/h4&gt;
&lt;p&gt;Critical issue?&lt;/p&gt;
&lt;p&gt;Push fix.&lt;/p&gt;
&lt;p&gt;Pipeline runs.&lt;/p&gt;
&lt;p&gt;Deploy.&lt;/p&gt;
&lt;p&gt;Done.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;real-world-example-1&quot;&gt;Real-World Example&lt;/h3&gt;
&lt;p&gt;Imagine updating a payment feature.&lt;/p&gt;
&lt;p&gt;Without CI/CD:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Manual tests&lt;/li&gt;
&lt;li&gt;Manual deploy&lt;/li&gt;
&lt;li&gt;High risk&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With CI/CD:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Push code&lt;/li&gt;
&lt;li&gt;Tests run automatically&lt;/li&gt;
&lt;li&gt;Deploy safely&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Huge difference.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;tools-you-ve-probably-heard-of&quot;&gt;Tools You’ve Probably Heard Of&lt;/h3&gt;
&lt;p&gt;Popular CI/CD tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;li&gt;GitLab CI/CD&lt;/li&gt;
&lt;li&gt;Jenkins&lt;/li&gt;
&lt;li&gt;CircleCI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Different tools.&lt;/p&gt;
&lt;p&gt;Same mission:&lt;/p&gt;
&lt;p&gt; Automate software delivery.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-modern-teams-can-t-ignore-it&quot;&gt;Why Modern Teams Can’t Ignore It&lt;/h3&gt;
&lt;p&gt;Today apps update constantly.&lt;/p&gt;
&lt;p&gt;Think about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Netflix&lt;/li&gt;
&lt;li&gt;Amazon&lt;/li&gt;
&lt;li&gt;Spotify&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They can’t do manual deployments all day.&lt;/p&gt;
&lt;p&gt;CI/CD makes modern scale possible.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-is-ci-cd-just-for-big-companies-&quot;&gt;⚠️ Is CI/CD Just for Big Companies?&lt;/h3&gt;
&lt;p&gt;No.&lt;/p&gt;
&lt;p&gt;Even small projects benefit.&lt;/p&gt;
&lt;p&gt;Because automation early saves pain later.&lt;/p&gt;
&lt;p&gt;Huge lesson.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-big-shift&quot;&gt;The Big Shift&lt;/h3&gt;
&lt;p&gt;CI/CD changed development from:&lt;/p&gt;
&lt;p&gt;❌ “Deploy carefully and hope”&lt;/p&gt;
&lt;p&gt;to&lt;/p&gt;
&lt;p&gt;✅ “Deploy confidently and often”&lt;/p&gt;
&lt;p&gt;That’s a mindset shift.&lt;/p&gt;
&lt;p&gt;Not just a tool shift.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thought&quot;&gt;Final Thought&lt;/h3&gt;
&lt;p&gt;CI/CD didn’t just make deployments faster.&lt;/p&gt;
&lt;p&gt; It changed how software gets built.&lt;/p&gt;
&lt;p&gt;Less fear.&lt;br&gt;
More automation.&lt;br&gt;
Faster delivery.&lt;/p&gt;
&lt;p&gt;And honestly…&lt;/p&gt;
&lt;p&gt;Modern software would look very different without it &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/15722/why-ci-cd-changed-software-development-forever</guid>
<pubDate>Fri, 24 Apr 2026 11:13:00 +0000</pubDate>
</item>
<item>
<title>How to Set Up Performance Budgets in CI/CD Pipelines</title>
<link>https://coderlegion.com/15046/how-to-set-up-performance-budgets-in-ci-cd-pipelines</link>
<description>&lt;p&gt;A performance budget in production is a line you refuse to cross. In CI it is the same line, enforced before a merge or deploy lands. Done well, the pipeline fails fast when a change regresses Core Web Vitals proxies, bundle weight, or your own custom thresholds, so you fix it in the branch instead of shipping first.&lt;/p&gt;&lt;p&gt;This guide assumes you already know &lt;em&gt;why&lt;/em&gt; budgets matter. For the full conceptual frame and metric tables, read &lt;a target=&quot;_blank&quot; rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/the-complete-guide-to-performance-budgets-for-web-teams&quot;&gt;The Complete Guide to Performance Budgets for Web Teams&lt;/a&gt;. Here we focus on wiring budgets into CI/CD: tools, config shapes, and operational pitfalls.&lt;/p&gt;&lt;h2&gt;What “performance budget in CI” usually means&lt;/h2&gt;&lt;p&gt;In pipelines, teams typically enforce lab metrics (Lighthouse scores and timings such as LCP, CLS, INP where available, TBT, and so on) against numeric ceilings or floors; resource budgets (caps on JS, CSS, image bytes, request counts, or third-party weight); or custom checks (synthetic steps that call your own scripts or APIs after a build).&lt;/p&gt;&lt;p&gt;Lighthouse CI is the common open path for lab metrics because it runs Lighthouse in a controlled environment, stores results, and supports assertions against budgets. Pair it with bundle analysers or size limits when regressions come from dependency drift rather than layout alone.&lt;/p&gt;&lt;p&gt;CI budgets are not a substitute for field data or scheduled monitoring across many URLs. They gate the build you are about to ship. Products such as &lt;a target=&quot;_blank&quot; rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com&quot;&gt;Apogee Watcher&lt;/a&gt; focus on ongoing lab schedules, portfolios, and alerts; see &lt;a target=&quot;_blank&quot; rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/how-to-set-up-automated-pagespeed-monitoring-for-multiple-sites&quot;&gt;How to Set Up Automated PageSpeed Monitoring for Multiple Sites&lt;/a&gt; for that workflow.&lt;/p&gt;&lt;h2&gt;Before you write YAML: pick URLs and environments&lt;/h2&gt;&lt;p&gt;CI runs should be deterministic enough to trust. Preview URLs from Netlify, Vercel, Cloudflare Pages, or internal preview hosts work if the pipeline waits until the deploy is reachable. Local static servers (&lt;code&gt;serve&lt;/code&gt;, &lt;code&gt;http-server&lt;/code&gt;) suit static sites; SPAs often need the production build and a correct &lt;code&gt;BASE_URL&lt;/code&gt;. Auth walls break Lighthouse unless you script login or use a dedicated test route.&lt;/p&gt;&lt;p&gt;Document which URLs represent home, a heavy template, and checkout or app shell if those differ. One URL with a loose budget hides regressions on another; at minimum, list primary templates in &lt;code&gt;lighthouserc&lt;/code&gt; or equivalent.&lt;/p&gt;&lt;h2&gt;Path 1: Lighthouse CI (LHCI)&lt;/h2&gt;&lt;h3&gt;Install&lt;/h3&gt;&lt;p&gt;Add Lighthouse CI to the repo (dev dependency is typical):&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install --save-dev @lhci/cli
&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Minimal &lt;code&gt;lighthouserc&lt;/code&gt; (assertions + collect)&lt;/h3&gt;&lt;p&gt;LHCI reads configuration from &lt;code&gt;lighthouserc.js&lt;/code&gt; or &lt;code&gt;lighthouserc.json&lt;/code&gt;. Example &lt;strong&gt;JSON&lt;/strong&gt; shape:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;ci&quot;: {
    &quot;collect&quot;: {
      &quot;url&quot;: [
        &quot;https://your-preview.example.com/&quot;,
        &quot;https://your-preview.example.com/pricing&quot;
      ],
      &quot;numberOfRuns&quot;: 3
    },
    &quot;assert&quot;: {
      &quot;assertions&quot;: {
        &quot;categories:performance&quot;: [&quot;error&quot;, { &quot;minScore&quot;: 0.9 }],
        &quot;largest-contentful-paint&quot;: [&quot;error&quot;, { &quot;maxNumericValue&quot;: 2500 }],
        &quot;cumulative-layout-shift&quot;: [&quot;error&quot;, { &quot;maxNumericValue&quot;: 0.1 }],
        &quot;total-blocking-time&quot;: [&quot;warn&quot;, { &quot;maxNumericValue&quot;: 300 }]
      }
    },
    &quot;upload&quot;: {
      &quot;target&quot;: &quot;temporary-public-storage&quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notes:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;numberOfRuns&lt;/code&gt;: multiple runs reduce noise; three is a common starting point.&lt;/li&gt;&lt;li&gt;Assertion keys map to Lighthouse audit IDs; align numeric budgets with your &lt;a target=&quot;_blank&quot; rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/performance-budget-thresholds-template&quot;&gt;performance budget template&lt;/a&gt; and team policy.&lt;/li&gt;&lt;li&gt;&lt;code&gt;upload.target&lt;/code&gt;: &lt;code&gt;temporary-public-storage&lt;/code&gt; is fine for getting started; teams often move to LHCI server or skip upload in pure gate mode.&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Wire the CI job&lt;/h3&gt;&lt;p&gt;Invoke LHCI after the app is built and the target URL responds. Typical flow:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;Install dependencies.&lt;/li&gt;&lt;li&gt;Build the site (if needed).&lt;/li&gt;&lt;li&gt;Deploy to preview or start a static server in the background.&lt;/li&gt;&lt;li&gt;Wait until the test URLs return HTTP 200.&lt;/li&gt;&lt;li&gt;Run &lt;code&gt;lhci autorun&lt;/code&gt; (or &lt;code&gt;lhci collect&lt;/code&gt; then &lt;code&gt;lhci assert&lt;/code&gt;).&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;If you use GitHub Actions, a dedicated job with &lt;code&gt;timeout-minutes&lt;/code&gt; and a health-check step avoids flaky “site not ready” failures. A minimal pattern is to probe the preview URL before &lt;code&gt;lhci autorun&lt;/code&gt;, for example with &lt;code&gt;curl -fsS --retry 5 --retry-delay 5 --retry-connrefused &quot;$PREVIEW_URL&quot;&lt;/code&gt;. &lt;code&gt;--retry-connrefused&lt;/code&gt; matters because a deploy that is not listening yet often returns “connection refused”, which plain &lt;code&gt;curl&lt;/code&gt; retries do not treat as transient by default. Store the same base URL in a CI variable and pass it into &lt;code&gt;lighthouserc&lt;/code&gt; or environment overrides your setup supports, so you do not duplicate hostnames in three places.&lt;/p&gt;&lt;h3&gt;Resource limits: &lt;code&gt;budgetsFile&lt;/code&gt; vs assertions&lt;/h3&gt;&lt;p&gt;Lighthouse CI can assert against a &lt;a target=&quot;_blank&quot; rel=&quot;nofollow&quot; href=&quot;https://github.com/GoogleChrome/budget.json&quot;&gt;budget.json&lt;/a&gt;-style file via &lt;code&gt;assert.budgetsFile&lt;/code&gt; (path relative to the working directory). In the upstream configuration model, that &lt;strong&gt;budgetsFile&lt;/strong&gt; mode is an alternative to filling &lt;code&gt;assert.assertions&lt;/code&gt; with audit IDs; it is not mixed with other &lt;code&gt;assert&lt;/code&gt; options in the same way. Check the &lt;a target=&quot;_blank&quot; rel=&quot;nofollow&quot; href=&quot;https://googlechrome.github.io/lighthouse-ci/docs/configuration.html&quot;&gt;Lighthouse CI configuration reference&lt;/a&gt; for the exact rules your CLI version supports.&lt;/p&gt;&lt;p&gt;If you want &lt;strong&gt;lab metrics&lt;/strong&gt; (LCP, CLS, and so on) and &lt;strong&gt;transfer or request budgets&lt;/strong&gt; in one &lt;code&gt;assertions&lt;/code&gt; block, use Lighthouse CI’s resource-summary assertions (for example &lt;code&gt;resource-summary:script:size&lt;/code&gt;, &lt;code&gt;resource-summary:third-party:count&lt;/code&gt;) alongside the audit keys. Sizes there use &lt;strong&gt;bytes&lt;/strong&gt; in assertion options; the standalone budget.json format often documents &lt;strong&gt;kilobytes&lt;/strong&gt;, so keep units straight when you copy numbers between files.&lt;/p&gt;&lt;p&gt;Whether you use a checked-in budget file or assertion rows, treat the file like any other policy: generate from your design system pipeline or review diffs in PRs so “max JS kilobytes” does not drift silently.&lt;/p&gt;&lt;h2&gt;Path 2: GitHub Actions sketch&lt;/h2&gt;&lt;p&gt;Below is a pattern, not a drop-in for every stack: replace build commands, Node version, and URL discovery with your own.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Lighthouse CI
on:
  pull_request:
    branches: [main, develop]

jobs:
  lhci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      # Example: wait for preview deploy via your provider’s CLI or API, then:
      - run: npx @lhci/cli autorun
        # Optional: set LHCI_GITHUB_APP_TOKEN if you use the Lighthouse CI GitHub App
        # so upload can post PR status checks. Not required for a local pass/fail gate.
        # env:
        #   LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Many teams split build and LHCI across workflows so preview deploy completes first; use &lt;code&gt;workflow_run&lt;/code&gt; or provider webhooks if needed. The critical invariant is that Lighthouse runs against the same artifact users will get, not a half-built tree.&lt;/p&gt;&lt;p&gt;On GitLab CI, CircleCI, or Buildkite the same steps apply: install Node, build, wait for URLs, then run &lt;code&gt;npx @lhci/cli autorun&lt;/code&gt; (or your package script). Cache &lt;code&gt;node_modules&lt;/code&gt; between runs when your runner allows it; cold installs dominate wall time on small changes.&lt;/p&gt;&lt;h2&gt;Resource budgets alongside Lighthouse&lt;/h2&gt;&lt;p&gt;Lab metrics can pass while JavaScript weight creeps up on every sprint. Add at least one of: bundle size limits (&lt;code&gt;bundlesize&lt;/code&gt;, &lt;code&gt;size-limit&lt;/code&gt;, or webpack’s &lt;code&gt;performance&lt;/code&gt; hints), or dependency reviews in the PR template so someone notices a multi-megabyte new dependency.&lt;/p&gt;&lt;p&gt;Treat resource budgets as complementary to LCP and CLS gates. A slow-LCP fix might add twenty kilobytes and still help users; a green Lighthouse run with a nine-hundred-kilobyte main bundle remains fragile.&lt;/p&gt;&lt;h2&gt;Flakiness and false positives&lt;/h2&gt;&lt;p&gt;CI environments are colder than a developer laptop. Variance in LCP and CLS is normal: mitigate with multiple runs, pinned Chrome, and stable network throttling settings in LHCI. Third-party ads or A/B scripts can differ run to run; block known domains in a test profile or point at a clean route. A cold CDN edge on the first request after deploy can skew timings; an optional warmup &lt;code&gt;GET&lt;/code&gt; before LHCI helps.&lt;/p&gt;&lt;p&gt;If the main branch is red every other day, teams stop trusting the job. Prefer warnings on noisy metrics and errors on stable ones until baselines settle.&lt;/p&gt;&lt;h2&gt;How this pairs with product-side budgets and alerts&lt;/h2&gt;&lt;p&gt;CI answers whether this change broke your thresholds on the URLs you chose. Scheduled monitoring answers whether you are still inside budget next week across the pages you track in production.&lt;/p&gt;&lt;p&gt;If you already use &lt;a target=&quot;_blank&quot; rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/product-spotlight-performance-budgets-email-alerts&quot;&gt;performance budgets and email alerts in Apogee Watcher&lt;/a&gt;, treat CI as the pre-merge gate and Watcher as the continuous check on real site inventories. Same vocabulary, different phase of the lifecycle.&lt;/p&gt;&lt;h2&gt;Apogee Watcher and CI: agency API (in active development)&lt;/h2&gt;&lt;p&gt;You do not need a vendor API to get value from Watcher next to LHCI. Many teams keep Lighthouse CI (or Lighthouse CLI) in the pipeline for fast feedback on the preview URL, and keep budgets, schedules, and digests in Apogee Watcher so production-facing URLs and client reporting stay in one product. Align the numbers: copy thresholds from your &lt;a target=&quot;_blank&quot; rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/performance-budget-thresholds-template&quot;&gt;performance budget template&lt;/a&gt; into both LHCI assertions and Watcher site budgets so “green in CI” and “green in monitoring” mean the same thing.&lt;/p&gt;&lt;p&gt;Apogee Watcher is building a &lt;strong&gt;plan-gated customer HTTP API&lt;/strong&gt; (under &lt;code&gt;/api/v1&lt;/code&gt;) aimed at agency workflows. &lt;strong&gt;It is in active development&lt;/strong&gt;: behaviour, routes, and reference documentation will firm up as releases land. Eligible plans will expose the capabilities below; exact rollout timing will follow release notes.&lt;/p&gt;&lt;p&gt;For agency users, the API will be supporting:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Check test results:&lt;/strong&gt; read latest (and historical) PageSpeed outcomes for monitored pages without opening the dashboard.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Trigger a new test:&lt;/strong&gt; request a fresh run after a deploy so CI or a script can wait on Watcher instead of calling the Google PageSpeed API key from your own workers.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Retrieve aggregated reports:&lt;/strong&gt; pull summary or roll-up reporting suitable for client packs or internal gates.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Retrieve historical trends:&lt;/strong&gt; chart-friendly series so pipelines or internal tools can compare this build against last week, not only the last run.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Why that matters next to LHCI: once those endpoints are live, a pipeline will be able to trigger a test, poll until results land, then fail if metrics breach the &lt;strong&gt;same&lt;/strong&gt; budgets you set in the app. Quota and retries will stay on Watcher’s side, and the failing run will remain tied to stored results and trends for client conversations, not only a line in your CI log.&lt;/p&gt;&lt;p&gt;Until the public reference and stable endpoints are published, keep using LHCI for branch gates and the Watcher UI for schedules and alerts. Follow the &lt;a target=&quot;_blank&quot; rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/category/product&quot;&gt;Product &amp;amp; Brand&lt;/a&gt; category on the blog for product news and API-related updates, and check plan details for API access when you are ready to wire automation. If you are interested in early beta tester access to the agency API, &lt;a target=&quot;_blank&quot; rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/contact&quot;&gt;contact us&lt;/a&gt;.&lt;/p&gt;&lt;h2&gt;Checklist: ship a credible CI budget&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;[ ] Listed representative URLs (not only &lt;code&gt;/&lt;/code&gt;).&lt;/li&gt;&lt;li&gt;[ ] Chose numeric thresholds aligned with your &lt;a target=&quot;_blank&quot; rel=&quot;nofollow&quot; href=&quot;https://apogeewatcher.com/blog/the-complete-guide-to-performance-budgets-for-web-teams&quot;&gt;complete guide&lt;/a&gt; or client contract.&lt;/li&gt;&lt;li&gt;[ ] Set &lt;code&gt;numberOfRuns&lt;/code&gt; ≥ 2 for stability.&lt;/li&gt;&lt;li&gt;[ ] Documented preview URL or static server startup in the workflow.&lt;/li&gt;&lt;li&gt;[ ] Added at least one resource or bundle guard for JS/CSS creep.&lt;/li&gt;&lt;li&gt;[ ] Separated &lt;code&gt;error&lt;/code&gt; vs &lt;code&gt;warn&lt;/code&gt; assertions to reduce alert fatigue.&lt;/li&gt;&lt;/ul&gt;&lt;hr&gt;&lt;h2&gt;FAQ&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Is Lighthouse CI the only option?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;No. Some teams wrap plain Lighthouse CLI, use Playwright traces, or rely on vendor-specific speed tools in CI. LHCI is widely documented and gives assertions + history-friendly uploads out of the box.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Should performance CI block every PR?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Often yes for &lt;code&gt;main&lt;/code&gt;, with optional paths for docs-only changes. Use path filters so markdown edits do not run full LHCI unless you want them to.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Can I enforce budgets without a preview deploy?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Yes for static sites: build, serve locally in CI, and point LHCI at &lt;code&gt;localhost&lt;/code&gt;. Dynamic server-rendered apps may need a test server with seed data.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Does this replace RUM or Search Console field data?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;No. Lab CI validates the candidate build; field metrics validate real users. Both belong in a mature performance program.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What if our budgets are stricter than Lighthouse in CI can reliably hit?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;Loosen CI thresholds slightly below production targets, or scope strict checks to stable audits (CLS, bundle size) and use &lt;strong&gt;warnings&lt;/strong&gt; for high-variance metrics until your environment is stable.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Can I trigger Apogee Watcher tests from CI with an API?&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;That workflow is &lt;strong&gt;in active development&lt;/strong&gt;. The customer API will support checking test results, triggering new tests, retrieving aggregated reports, and retrieving historical trends from automation for agency users on eligible plans (subject to published docs and plan limits). It is not a stable, copy-paste integration yet. For today’s deploy gates, use LHCI or Lighthouse in CI; keep Watcher for scheduled runs, budgets, and alerts. When the reference documentation and endpoints are public, they will be the supported way to align CI with dashboard budgets without putting a PageSpeed API key in your pipeline.&lt;/p&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/15046/how-to-set-up-performance-budgets-in-ci-cd-pipelines</guid>
<pubDate>Sun, 19 Apr 2026 23:55:33 +0000</pubDate>
</item>
<item>
<title>The Validation Bottleneck: Why Testing Is the New Speed Limit</title>
<link>https://coderlegion.com/14457/the-validation-bottleneck-why-testing-is-the-new-speed-limit</link>
<description>&lt;p&gt;AI coding agents can generate code in seconds. Your CI/CD pipeline cannot keep up.&lt;/p&gt;
&lt;p&gt;That gap is the next major problem in software development. And it's one the industry is only beginning to take seriously.&lt;/p&gt;
&lt;p&gt;For most engineering teams, the development feedback loop has three rough stages: write code, test it locally, push it through a CI/CD pipeline. The middle stage — local testing — has always been a limited proxy for what happens in production. Unit tests pass. Integration tests catch something else. Something breaks in staging that didn't show up anywhere else.&lt;/p&gt;
&lt;p&gt;Agents don't change this dynamic. They make it worse.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;More code, same pipeline&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When a single developer using an AI agent can produce code at the pace that used to require a team, the volume of pull requests entering your pipeline goes up dramatically. The pipeline itself doesn't scale automatically. It's still spinning up test environments, running sequential jobs, waiting on staging queues.&lt;/p&gt;
&lt;p&gt;&quot;AI has solved everything but what mirrord does,&quot; said Aviram Hassan, CEO and co-founder of MetalBear. &quot;You can generate code using AI. You can test it at a sort of local level — unit testing, component testing. You can review code. But once it comes to integration testing, you don't really have a good solution. And so that bottleneck just becomes more dominant, because there's more code that needs to be tested using those same limited resources.&quot;&lt;/p&gt;
&lt;p&gt;Hassan's company, MetalBear, builds mirrord — a tool that lets local code run directly against a live Kubernetes cluster environment rather than a simulated local version. The premise is straightforward: local mocks don't reflect production conditions, and dedicated per-environment test setups are too slow and expensive to scale when agents are generating code at this pace.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integration testing is the hard part&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The layers of testing that AI assists with most naturally are the ones with the most deterministic feedback — unit tests and static analysis. Code either passes or it doesn't. An agent can read the error and try again.&lt;/p&gt;
&lt;p&gt;Integration testing is different. It requires a full application context: connected databases, running services, real API dependencies. Setting up that context for each agentic code change — especially if you're running multiple agents in parallel — means either sharing a bottlenecked staging environment or spinning up dedicated environments that take time and cost money.&lt;/p&gt;
&lt;p&gt;MetalBear's approach is to let agents access a shared production-like Kubernetes environment with traffic filtering and database isolation so multiple agents can test concurrently without stepping on each other. Early adopters have reported significantly faster CI cycle times compared to managing per-run ephemeral environments.&lt;/p&gt;
&lt;p&gt;The broader point goes beyond any single tool. Traditional CI/CD pipelines were designed for a world where code moved at human speed. The architecture assumed a certain volume of commits, a certain frequency of deployments, a certain amount of time between changes. That assumption is breaking down.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What engineering teams need to rethink&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The development pipeline needs to evolve in the same direction as the agents themselves — toward concurrency and faster feedback loops. A few practical shifts worth considering:&lt;/p&gt;
&lt;p&gt;First, start measuring where your pipeline actually spends its time. In a lot of organizations, the majority of CI time is consumed by environment provisioning, not test execution. That's an infrastructure problem, not a testing problem, and it's often addressable without rearchitecting everything.&lt;/p&gt;
&lt;p&gt;Second, think carefully about what local tests can and cannot tell you. An agent that passes unit tests is not necessarily producing code that works in your environment. The faster agents get, the more important it becomes to close that gap quickly rather than after a full pipeline run.&lt;/p&gt;
&lt;p&gt;Third, parallel agent testing requires explicit isolation strategies. Shared staging environments were barely manageable when individual developers were competing for them. When multiple agents are running against the same environment simultaneously, you need deliberate approaches to traffic routing, database state, and queue handling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The speed limit has moved&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For most of the last decade, the speed limit on software delivery was writing code. Developers were the bottleneck. AI agents have shifted that constraint.&lt;/p&gt;
&lt;p&gt;The new speed limit is validation. And unless testing infrastructure catches up, the productivity gains from agentic development will be partially absorbed by a pipeline that wasn't designed to handle them.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14457/the-validation-bottleneck-why-testing-is-the-new-speed-limit</guid>
<pubDate>Mon, 13 Apr 2026 13:00:00 +0000</pubDate>
</item>
<item>
<title>Deploying a Production-Ready React App on AWS using Terraform Module, S3, CloudFront &amp; GitHub Actions</title>
<link>https://coderlegion.com/14231/deploying-production-ready-react-app-using-terraform-module-cloudfront-github-actions</link>
<description>&lt;p&gt;Modern frontend apps need fast, scalable, and cost-efficient hosting. &lt;br&gt;
While AWS S3 is great for static hosting, making it production-ready &lt;br&gt;
requires CloudFront, proper security, and automation.&lt;/p&gt;
&lt;p&gt;In this blog, I’ll show how I built a fully automated pipeline. &lt;br&gt;
to deploy a React app using Terraform and GitHub Actions.&lt;/p&gt;
&lt;h3 id=&quot;architecture-overview&quot;&gt;Architecture Overview&lt;/h3&gt;
&lt;p&gt;Here’s what we’re building:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;React app → built into static files&lt;/li&gt;
&lt;li&gt;Terraform provisions:&lt;ul&gt;
&lt;li&gt;S3 bucket (hosting)&lt;/li&gt;
&lt;li&gt;CloudFront distribution (CDN)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Actions:&lt;ul&gt;
&lt;li&gt;Build React app&lt;/li&gt;
&lt;li&gt;Upload to S3&lt;/li&gt;
&lt;li&gt;Invalidate CloudFront cache&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cuk8w1wo5w7ubt0rpkll.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;project-structure&quot;&gt;Project Structure&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/syakprqqr7dxz8kd1mmb.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;-step-1-terraform-module-design&quot;&gt;⚙️ Step 1: Terraform Module Design&lt;/h3&gt;
&lt;p&gt;Instead of writing everything in one file, I used modular Terraform.&lt;/p&gt;
&lt;h3 id=&quot;example-modules-&quot;&gt;&lt;strong&gt;Example modules:&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt; How Modules Are Used (Root Configuration)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This snippet shows how the root module calls the S3 and CloudFront &lt;br&gt;
modules to provision infrastructure.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-terraform&quot;&gt;module &quot;s3_static_site&quot; {
  source      = &quot;./modules/s3-static-site&quot;
  bucket_name = var.bucket_name
}

module &quot;cloudfront&quot; {
  source             = &quot;./modules/cloudfront-distribution&quot;
  bucket_domain_name = module.s3_static_site.bucket_domain_name
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;s3-module-static-hosting-&quot;&gt;S3 Module (Static Hosting)&lt;/h3&gt;
&lt;p&gt;This module creates and configures the S3 bucket for static hosting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Bucket Creation&lt;/strong&gt;&lt;br&gt;
This snippet creates the S3 bucket that will host the React static site.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-terraform&quot;&gt;resource &quot;aws_s3_bucket&quot; &quot;skr_bucket&quot; {
  bucket = var.bucket_name
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Website Configuration&lt;/strong&gt;&lt;br&gt;
This snippet configures the S3 bucket for static website hosting and sets the index document.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-terraform&quot;&gt;resource &quot;aws_s3_bucket_website_configuration&quot; &quot;skr_bucket&quot; {
  bucket = aws_s3_bucket.skr_bucket.id

  index_document {
    suffix = &quot;index.html&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Bucket Policy (for CloudFront)&lt;/strong&gt;&lt;br&gt;
This snippet sets a bucket policy allowing CloudFront (and other services if needed) to read objects from the S3 bucket.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-terraform&quot;&gt;resource &quot;aws_s3_bucket_policy&quot; &quot;allow_cloudfront&quot; {
  bucket = aws_s3_bucket.skr_bucket.id

  policy = jsonencode({
    Statement = [{
      Effect = &quot;Allow&quot;
      Principal = &quot;*&quot;
      Action = &quot;s3:GetObject&quot;
      Resource = &quot;${aws_s3_bucket.skr_bucket.arn}/*&quot;
    }]
  })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;cloudfront-module&quot;&gt;CloudFront Module&lt;/h3&gt;
&lt;p&gt;This module sets up a CDN in front of S3.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Distribution&lt;/strong&gt;&lt;br&gt;
This snippet creates the CloudFront distribution that serves your S3 bucket globally with HTTPS support.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-terraform&quot;&gt;resource &quot;aws_cloudfront_distribution&quot; &quot;cdn&quot; {
  enabled             = true
  default_root_object = &quot;index.html&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;React Routing Fix (IMPORTANT)&lt;/strong&gt;&lt;br&gt;
This snippet ensures single-page React routes return index.html instead of 404 errors.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-terraform&quot;&gt;custom_error_response {
  error_code         = 404
  response_code      = 200
  response_page_path = &quot;/index.html&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Cache Invalidation&lt;/strong&gt;&lt;br&gt;
This snippet invalidates the CloudFront cache automatically after deployment, so new changes appear immediately.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-terraform&quot;&gt;resource &quot;null_resource&quot; &quot;cache&quot; {
  provisioner &quot;local-exec&quot; {
    command = &quot;aws cloudfront create-invalidation --distribution-id ${aws_cloudfront_distribution.cdn.id} --paths '/*'&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This Terraform module-based architecture makes it easy to reuse infrastructure across multiple environments like dev, staging, and production.&lt;/p&gt;
&lt;h3 id=&quot;benefits-&quot;&gt;Benefits:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Reusable&lt;/li&gt;
&lt;li&gt;Clean architecture&lt;/li&gt;
&lt;li&gt;Easier debugging&lt;/li&gt;
&lt;li&gt;Production-ready structure&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-step-2-s3-static-website-hosting&quot;&gt;☁️ Step 2: S3 Static Website Hosting&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;The S3 module:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creates a bucket&lt;/li&gt;
&lt;li&gt;Enables static hosting&lt;/li&gt;
&lt;li&gt;Configures public access (Via OAC)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Versioning enabled&lt;/li&gt;
&lt;li&gt;Proper bucket policy&lt;/li&gt;
&lt;li&gt;Index + error documents&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;step-3-cloudfront-cdn-setup&quot;&gt;Step 3: CloudFront CDN Setup&lt;/h3&gt;
&lt;p&gt;CloudFront sits in front of S3 to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Improve performance (low latency)&lt;/li&gt;
&lt;li&gt;Add HTTPS support&lt;/li&gt;
&lt;li&gt;Cache content globally&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Configuration highlights:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Origin: S3 bucket&lt;/li&gt;
&lt;li&gt;Viewer protocol: Redirect HTTP → HTTPS&lt;/li&gt;
&lt;li&gt;Cache behavior optimized for static assets&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-step-4-github-actions-ci-cd&quot;&gt;⚡ Step 4: GitHub Actions CI/CD&lt;/h3&gt;
&lt;p&gt;This is where things get powerful.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow does:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install dependencies&lt;/li&gt;
&lt;li&gt;Build React app&lt;/li&gt;
&lt;li&gt;Sync build folder to S3&lt;/li&gt;
&lt;li&gt;Invalidate CloudFront cache&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every push to main → automatic deployment &lt;/p&gt;
&lt;h3 id=&quot;ci-cd-workflow-example&quot;&gt;CI/CD Workflow Example&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Deploy to CloudFront

on:
  push:
    branches:
      - main
  workflow_dispatch:

env:
  AWS_REGION: ap-south-1
  S3_BUCKET: samir-module-s3-bucket-hosting

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          cache-dependency-path: static-site-react/package-lock.json

      - name: Install dependencies
        working-directory: ./static-site-react
        run: npm ci

      - name: Build React app
        working-directory: ./static-site-react
        run: npm run build

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Upload to S3
        run: |
          aws s3 sync ./static-site-react/dist/ s3://${{ env.S3_BUCKET }} --delete

      - name: Get CloudFront distribution ID
        id: cloudfront
        run: |
          DISTRIBUTION_ID=$(aws cloudfront list-distributions --query &quot;DistributionList.Items[?Origins.Items[0].DomainName=='${{ env.S3_BUCKET }}.s3.${{ env.AWS_REGION }}.amazonaws.com'].Id&quot; --output text)
          echo &quot;distribution_id=$DISTRIBUTION_ID&quot; &amp;gt;&amp;gt; $GITHUB_OUTPUT

      - name: Invalidate CloudFront cache
        run: |
          aws cloudfront create-invalidation --distribution-id ${{ steps.cloudfront.outputs.distribution_id }} --paths &quot;/*&quot;

      - name: Deploy success message
        run: |
          echo &quot;✅ Deployment completed successfully!&quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why Use Terraform Modules?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Without modules:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Messy code&lt;/li&gt;
&lt;li&gt;Hard to reuse&lt;/li&gt;
&lt;li&gt;Difficult scaling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;With modules:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Clean separation&lt;/li&gt;
&lt;li&gt;Reusable across projects&lt;/li&gt;
&lt;li&gt;Easier team collaboration&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;key-advantages-of-this-setup&quot;&gt;Key Advantages of This Setup&lt;/h3&gt;
&lt;p&gt;✅ Fully automated deployments&lt;br&gt;
✅ Global CDN performance&lt;br&gt;
✅ Infrastructure as Code (IaC)&lt;br&gt;
✅ Scalable &amp;amp; production-ready&lt;br&gt;
✅ Low cost (S3 + CloudFront)&lt;/p&gt;
&lt;h3 id=&quot;how-to-run-this-project&quot;&gt;How to Run This Project&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Initialize Terraform&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;terraform init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kndv10jzdnierit9vbyl.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Apply Infrastructure&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot;&gt;terraform apply -auto-approve
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d4n4x37lmy9ki9x87ypn.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deploy React App&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Push code to GitHub → CI/CD handles the rest&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p7lta3vhszxqeiuat26g.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;best-practices&quot;&gt;Best Practices&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Use IAM roles instead of access keys&lt;/li&gt;
&lt;li&gt;Store secrets in GitHub Secrets&lt;/li&gt;
&lt;li&gt;Enable CloudFront caching strategies&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;improvements-you-can-add&quot;&gt;Improvements You Can Add&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Custom domain with Route 53&lt;/li&gt;
&lt;li&gt;HTTPS with ACM&lt;/li&gt;
&lt;li&gt;WAF for security&lt;/li&gt;
&lt;li&gt;Multi-environment Terraform setup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Conclusion&lt;/p&gt;
&lt;p&gt;This project demonstrates how to build a modern frontend deployment pipeline using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Terraform Modules&lt;/li&gt;
&lt;li&gt;AWS S3 + CloudFront&lt;/li&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;final-output&quot;&gt;Final Output&lt;/h3&gt;
&lt;p&gt;After deployment, your app will be available via:&lt;/p&gt;
&lt;p&gt; CloudFront URL (fast, secure, global)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qkfm2xp88vncgd4qr96u.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;explore-the-code&quot;&gt;Explore the Code&lt;/h3&gt;
&lt;p&gt;The full project with Terraform modules, S3, CloudFront setup, and GitHub Actions workflow is available here:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Shawmeer/s3-website-hosting-using-terraform-modules&quot;&gt;Check it out on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; With this setup, you now have a fully automated, scalable, and production-ready React deployment pipeline. Start building modern frontends with confidence!Happy Building!!&lt;/p&gt;
&lt;h3 id=&quot;connect-with-me&quot;&gt;Connect with me&lt;/h3&gt;
&lt;p&gt;• Portfolio: &lt;a rel=&quot;nofollow&quot; href=&quot;https://khanalsamir.com&quot;&gt;https://khanalsamir.com&lt;/a&gt;&lt;br&gt;
• GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Shawmeer&quot;&gt;https://github.com/Shawmeer&lt;/a&gt;&lt;br&gt;
• LinkedIn: &lt;a rel=&quot;nofollow&quot; href=&quot;https://linkedin.com/in/samir-khanal&quot;&gt;https://linkedin.com/in/samir-khanal&lt;/a&gt;  &lt;/p&gt;
&lt;p&gt;If you're working on similar DevOps or AWS projects, feel free to connect. I regularly share practical cloud and CI/CD content.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14231/deploying-production-ready-react-app-using-terraform-module-cloudfront-github-actions</guid>
<pubDate>Wed, 08 Apr 2026 11:41:00 +0000</pubDate>
</item>
<item>
<title>The Secret Behind SAST: The Security Blind Spot Developers Can’t Ignore (Part 1)</title>
<link>https://coderlegion.com/9856/the-secret-behind-sast-the-security-blind-spot-developers-cant-ignore-part-1</link>
<description>&lt;h3 id=&quot;itroduction&quot;&gt;Itroduction&lt;/h3&gt;
&lt;p&gt;The first part of this article provides a robust theoretical foundation for understanding SAST and its importance in ensuring secure development. Additionally, the subsequent articles (Part 2, and Part 3) will delve into practical examples and effective implementation strategies.&lt;/p&gt;
&lt;p&gt;In the field of modern software engineering, it is not only required to provide features on time but also to ensure their security. As companies refine their Secure Software Development Life Cycle (SSDLC) strategies, one essential aspect that stands out is Static Application Security Testing (SAST).&lt;/p&gt;
&lt;h3 id=&quot;what-is-sast-&quot;&gt;What is SAST?&lt;/h3&gt;
&lt;p&gt;SAST, or Static Application Security Testing, refers to a type of security testing that identifies and addresses potential vulnerabilities in software code. SAST involves examining source code, bytecode, or binaries without running the actual application by scanning the codebase for known vulnerability patterns. &lt;/p&gt;
&lt;p&gt;Source code refers to the human-readable code written by developers.&lt;/p&gt;
&lt;p&gt;Bytecode refers to the compiled form of source code that a virtual machine or runtime can execute, which can be analyzed by SAST tools to detect errors, vulnerabilities, or performance issues before deployment. For example, in Java .class files – the compiled Java classes generated from .java source files.&lt;/p&gt;
&lt;p&gt;Binaries refer to compiled artifacts; in Java, these are typically .jar files, which are Java archives containing .class files and sometimes additional resources such as properties or XML files.&lt;/p&gt;
&lt;p&gt;This tool can detect security weaknesses early on, which covers bugs, code smells, and the latest at this moment, 2021 OWASP TOP 10 categories, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://owasp.org/Top10/A01_2021-Broken_Access_Control/&quot;&gt;A01:2021-Broken Access Control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://owasp.org/Top10/A02_2021-Cryptographic_Failures/&quot;&gt;A02:2021-Cryptographic Failures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://owasp.org/Top10/A03_2021-Injection/&quot;&gt;A03:2021-Injection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://owasp.org/Top10/A04_2021-Insecure_Design/&quot;&gt;A04:2021-Insecure Design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://owasp.org/Top10/A05_2021-Security_Misconfiguration/&quot;&gt;A05:2021-Security Misconfiguration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://owasp.org/Top10/A06_2021-Vulnerable_and_Outdated_Components/&quot;&gt;A06:2021-Vulnerable and Outdated Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/&quot;&gt;A07:2021-Identification and Authentication Failures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://owasp.org/Top10/A08_2021-Software_and_Data_Integrity_Failures/&quot;&gt;A08:2021-Software and Data Integrity Failures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/&quot;&gt;A09:2021-Security Logging and Monitoring Failures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/&quot;&gt;A10:2021-Server-Side Request Forgery&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SAST tools analyze program components, including code structure, data flows, and control flows, to identify potential security vulnerabilities before the software is developed or deployed.&lt;/p&gt;
&lt;p&gt;Control flow refers to the order in which individual statements, instructions, or functions in a program are executed.&lt;/p&gt;
&lt;p&gt;Data flow refers to the movement of data through a program, specifically how values are assigned, modified, and passed between variables, functions, or modules. It defines how data travels and transforms during execution. One example is detecting tainted data. Tainted data refers to identifying untrusted or potentially dangerous input in a program that could flow into sensitive operations (called “sinks”) without proper validation or sanitization.&lt;/p&gt;
&lt;p&gt;Control flow determines the sequence of execution in a program, while data flow tracks how data values propagate and transform through that execution.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Aspect   &lt;/th&gt;&lt;th&gt;Control Flow &lt;/th&gt;&lt;th&gt;Data flow&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;What it describes        &lt;/td&gt;&lt;td&gt;Execution order or decision paths      &lt;/td&gt;&lt;td&gt;How data moves and changes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Examples      &lt;/td&gt;&lt;td&gt;Loops, conditionals, function calls   &lt;/td&gt;&lt;td&gt;Variable assignments, method parameters, and return values&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Use in SAST      &lt;/td&gt;&lt;td&gt;Detect unreachable code, infinite loops, and so on    &lt;/td&gt;&lt;td&gt;Detect tainted data, SQL injection, and logging sensitive info&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Some of the tools classified as SAST are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Semgrep, PMD, SpotBugs, and FindSecBugs - popular open-source tools for code analysis and bug detection.&lt;/li&gt;
&lt;li&gt;Fortify, Checkmarx, and Veracode -  reliable enterprise solutions.&lt;/li&gt;
&lt;li&gt;Snyk, SonarQube Cloud, and GitHub CodeQL - cloud-based or SaaS tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-importance-of-sast-in-secure-development-cannot-be-overstated-&quot;&gt;The importance of SAST in secure development cannot be overstated.&lt;/h3&gt;
&lt;p&gt;Implementing SAST within SSDLC enables the efficient implementation of large-scale security measures by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Detecting problems early on&lt;/li&gt;
&lt;li&gt;Halting the advancement of vulnerable code through the pipeline, SAST minimizes both time and expense associated with remediation.&lt;/li&gt;
&lt;li&gt;Incorporating security earlier in the development process&lt;/li&gt;
&lt;li&gt;Feedback is provided to developers during the commit or pull request process while the context is still fresh, allowing for any necessary fixes to be made more easily.&lt;/li&gt;
&lt;li&gt;Promoting the education of developers.&lt;/li&gt;
&lt;li&gt;SAST findings facilitate the organic acquisition of secure coding patterns by developers, promoting the integration of security as a daily practice.&lt;/li&gt;
&lt;li&gt;Minimizing risk in the manufacturing process&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By addressing vulnerabilities early on in the SDLC or live systems, there is a lower occurrence of security incidents. Several compliance frameworks, such as ISO 27001, SOC2, and PCI DSS, require the inclusion of static security testing in secure development governance.&lt;/p&gt;
&lt;h3 id=&quot;how-to-integrate-sast-into-ci-cd-&quot;&gt;How to integrate SAST into CI/CD?&lt;/h3&gt;
&lt;p&gt;The integration of SAST into the development lifecycle is essential for its optimal effectiveness.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Developer local environment (can be optional, but recommended)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run lightweight SAST tools locally (Semgrep, CodeQL CLI, Snyk CLI, SonarQube local server using Docker) or run IDE plugins such as SonarQube Cloud that can connect easily from the IDE to the SonarQube Cloud server before pushing code.&lt;/li&gt;
&lt;li&gt;Helps reduce noise in the CI pipeline or incidentally pushes sensitive data to the GIT repository.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Pull request (PR) and merge request (MR) stage&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Protect the main branches from merging directly until all pipeline jobs are &lt;strong&gt;SUCCESSFUL&lt;/strong&gt;. If Git Flow is used, protected branches typically are: &lt;strong&gt;dev&lt;/strong&gt;, &lt;strong&gt;qa&lt;/strong&gt;, or &lt;strong&gt;staging&lt;/strong&gt;, &lt;strong&gt;pre-prod&lt;/strong&gt;, and &lt;strong&gt;prod&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Trigger a SAST scan on &lt;strong&gt;each PR/MR&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Block merging if findings exceed severity thresholds, for example, by configuring Quality Gate in SonarQube or other severity-related policies. You can configure the CI/CD pipeline to &lt;strong&gt;FAIL&lt;/strong&gt; if, for example, &lt;strong&gt;1 Critical&lt;/strong&gt; and/or &lt;strong&gt;3 High&lt;/strong&gt; vulnerabilities are detected.&lt;/li&gt;
&lt;li&gt;Use the SAST tool’s suggestions on how to resolve the issue.&lt;/li&gt;
&lt;li&gt;Investigate the findings to identify potential &lt;strong&gt;false positives&lt;/strong&gt;. SAST scanners provide useful information about each vulnerability, making it easier to understand them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;CI pipeline&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add SAST as an early stage. Depends on the project; it can be after the build and test stage or before it, if only the source code is used to scan the project, but the recommended approach is to use it after, because you can configure it to scan for binaries or bytecode.&lt;/li&gt;
&lt;li&gt;Fail builds based on:&lt;/li&gt;
&lt;li&gt;severity level (Low, Medium, High, Critical)&lt;/li&gt;
&lt;li&gt;the count of severity levels (for example, 20 Medium, 5 High, 2 Critical)&lt;/li&gt;
&lt;li&gt;security policy rules&lt;/li&gt;
&lt;li&gt;code smells&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Scheduled full scans&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run nightly or weekly deep scans on the entire codebase. For example, Snyk scans the project by default once a week and sends the report to the user’s email (this is a SaaS tool, and it depends on whether the company is allowed to send the codebase to other services for scanning for vulnerabilities). &lt;/li&gt;
&lt;li&gt;Capture findings not detected in incremental scans.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Governance&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Export results into an issue tracker (such as Jira) or a SIEM (Security Information and Event Management) system. Some of the SAST tools allow the creation of an issue ticket in Jira.&lt;/li&gt;
&lt;li&gt;Track remediation SLAs (Service Level Agreement)  by severity:&lt;/li&gt;
&lt;li&gt;Critical issues fixed within &lt;strong&gt;24 hours&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;High-severity fix within &lt;strong&gt;3 days&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Medium &lt;strong&gt;7–14 days&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Low &lt;strong&gt;30+ days&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maintain centralized dashboards (SonarQube, Snyk, DefectDojo).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key principle: &lt;strong&gt;SAST must become a non-negotiable quality gate&lt;/strong&gt;, just like unit tests, linters, or build verifiers.&lt;/p&gt;
&lt;h3 id=&quot;why-do-some-companies-run-sast-within-cvs-instead-of-saas-tools-&quot;&gt;Why do some companies run SAST within CVS instead of SaaS tools?&lt;/h3&gt;
&lt;p&gt;While modern SaaS security platforms are powerful, many enterprises still &lt;strong&gt;run SAST inside their version-control system (CVS)&lt;/strong&gt; or self-hosted environments. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data protection and compliance&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Source code is often the company’s most sensitive intellectual property.&lt;br&gt;
Some industries, such as finance, defense, and telecom, cannot upload code to external SaaS due to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data residency restrictions&lt;/li&gt;
&lt;li&gt;Regulatory constraints&lt;/li&gt;
&lt;li&gt;Confidentiality concerns&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Full control and custom rules&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Self-hosted SAST (SonarQube Enterprise, Checkmarx On-Premise) offers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Custom rule tuning for domain-specific patterns&lt;/li&gt;
&lt;li&gt;Control over scanner performance and scheduling&lt;/li&gt;
&lt;li&gt;On-prem log retention and audit trails&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;No external vendor dependency&lt;/strong&gt;&lt;br&gt;
Large organizations want predictable SLAs and reliability without relying on external service uptime or rate limits. Companies often use cloud VMs (Virtual Machines) with autoscaling that make the server available even if one goes down, especially when all infrastructure is deployed on a specific cloud provider like AWS, Azure, and GCP. Still, it is more difficult to configure and maintain your own server compared to SaaS alternatives.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cost optimization at scale&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SaaS SAST tools are charged per seat or LOC (lines of code).&lt;br&gt;
For large organizations with millions of LOC, on-premise licensing might be more efficient.&lt;br&gt;
This doesn't make SaaS solutions inferior; they are excellent for small to mid-sized companies or teams that prefer zero maintenance overhead, but big enterprises often choose to keep scanning where the codebase is located or self-hosted.&lt;/p&gt;
&lt;h3 id=&quot;how-does-sast-strengthen-your-application-before-deployment-&quot;&gt;How does SAST strengthen your application before deployment?&lt;/h3&gt;
&lt;p&gt;SAST adds many layers of security reinforcement before one line is deployed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Eliminates vulnerabilities at the earliest point. Fixing issues in development prevents vulnerabilities from ever entering later stages.&lt;/li&gt;
&lt;li&gt;Reduces attack surface. Every resolved injection flaw, every sanitized input, and every removed secret reduces the exploitable surface in production.&lt;/li&gt;
&lt;li&gt;Improved code quality. Most of the SAST rules lead to improved code clarity, design patterns, and maintainability.&lt;/li&gt;
&lt;li&gt;Drives security culture. SAST is an enabler of secure software development practices. Teams start thinking in terms of:&lt;/li&gt;
&lt;li&gt;safe patterns&lt;/li&gt;
&lt;li&gt;least-privilege design&lt;/li&gt;
&lt;li&gt;secure defaults&lt;/li&gt;
&lt;li&gt;flow validation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;SAST best practices&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To maximize the value of SAST in SSDLC:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Start with a minimal and focused rule set. Avoid overwhelming developers. Enable rules progressively.&lt;/li&gt;
&lt;li&gt;Integrate into PR pipelines.&lt;/li&gt;
&lt;li&gt;Make SAST feedback part of the daily development routine.&lt;/li&gt;
&lt;li&gt;Use severity thresholds. These configurations can differ from company to company.&lt;/li&gt;
&lt;li&gt;Avoid false positives. Not all findings are equal; that is why you need to tune rules to reduce false positives. Even if the finding’s severity level is High and it may appear to be a serious issue, it depends on how the feature or script was implemented.&lt;/li&gt;
&lt;li&gt;Establish SLAs for remediation.&lt;br&gt;
For example:&lt;/li&gt;
&lt;li&gt;Critical: fix within 1-2 days.&lt;/li&gt;
&lt;li&gt;High: fix within 5-7 days.&lt;/li&gt;
&lt;li&gt;Medium: fix within 30 days.&lt;/li&gt;
&lt;li&gt;Combine with other AST tools. SAST is one part of the security puzzle. Always complement it with:&lt;/li&gt;
&lt;li&gt;ISCA (dependency scanning), if the tool does not support it, or you can use the free trial version of the tool.&lt;/li&gt;
&lt;li&gt;WAF (Web Application Firewall) adds a defence layer in front of your application by filtering and monitoring HTTP/HTTPS traffic.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; WAF does not replace SAST or DAST. It can block known attack patterns, mitigate exploitation attempts before fixes are deployed, provide real-time protection in production, and prevent DDoS attacks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DAST (Dynamic Application Security Testing). Scan the &lt;strong&gt;DEV&lt;/strong&gt; or &lt;strong&gt;QA&lt;/strong&gt; environment, because &lt;strong&gt;DAST&lt;/strong&gt; is used when the application is up and running. &lt;br&gt;
Effective DAST can be performed in two phases:&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Automated scanning&lt;/strong&gt; — baseline detection of common runtime vulnerabilities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Manual exploratory testing&lt;/strong&gt; — for issues that automated tools often miss (logic flaws, authorization bypasses, etc.). Not all vulnerabilities can be discovered by SAST alone, which is why DAST is an important complement.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ensure DevSecOps collaboration.&lt;br&gt;
Security engineers, platform teams, and developers must collaborate on:&lt;/li&gt;
&lt;li&gt;rule tuning&lt;/li&gt;
&lt;li&gt;prioritization&lt;/li&gt;
&lt;li&gt;education&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;SAST remains one of the cornerstones of a mature &lt;strong&gt;SSDLC&lt;/strong&gt;. It provides early, actionable insights into code security, reduces risk, empowers developers, and fortifies applications before deployment.&lt;br&gt;
Implemented with care, &lt;strong&gt;SAST&lt;/strong&gt; becomes more than just a detection tool; it becomes a catalyst for a long-term secure engineering culture.&lt;/p&gt;
&lt;h3 id=&quot;closing&quot;&gt;Closing&lt;/h3&gt;
&lt;p&gt;This article covered the theoretical foundation of &lt;strong&gt;SAST&lt;/strong&gt; and its strategic role within secure development. In ”&lt;strong&gt;Catch vulnerabilities before they ship: local SonarQube setup (Part 2)&lt;/strong&gt;”, we will explore SonarQube Cloud configuration and IntelliJ IDEA integration, as well as running SonarQube locally with Docker.&lt;br&gt;
If you're building secure systems at scale, &lt;strong&gt;SAST&lt;/strong&gt; is where prevention truly begins.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/9856/the-secret-behind-sast-the-security-blind-spot-developers-cant-ignore-part-1</guid>
<pubDate>Fri, 16 Jan 2026 16:13:02 +0000</pubDate>
</item>
<item>
<title>Hello World to GitHub Actions: Your First Automated Workflow</title>
<link>https://coderlegion.com/8878/hello-world-to-github-actions-your-first-automated-workflow</link>
<description>&lt;p&gt;Hey there! So you're building a React app with TypeScript and Vite, and you keep hearing people talk about CI/CD and GitHub Actions. What's all the fuss about? Let me break it down for you in plain English.&lt;/p&gt;
&lt;h3 id=&quot;what-s-github-actions-anyway-&quot;&gt;What's GitHub Actions Anyway?&lt;/h3&gt;
&lt;p&gt;Think of GitHub Actions as your automation buddy that lives in your GitHub repo. You tell it &quot;hey, whenever I push code, run my tests and make sure nothing broke,&quot; and it just... does it. Automatically. While you grab coffee(or chai) or work on something else.&lt;/p&gt;
&lt;p&gt;Pretty cool, right? Here's what makes it awesome:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's automatic&lt;/strong&gt; - No more remembering to run tests before you push. Your automation buddy handles it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's consistent&lt;/strong&gt; - Everyone's code gets tested the same way, every single time. No more &quot;but it works on my machine!&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's free&lt;/strong&gt; - GitHub gives you a generous amount of free minutes every month. For most personal projects, you won't pay a dime.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's built-in&lt;/strong&gt; - Everything happens right in GitHub. No need to sign up for yet another service.&lt;/p&gt;
&lt;h3 id=&quot;the-basics-don-t-worry-it-s-simple-&quot;&gt;The Basics (Don't Worry, It's Simple!)&lt;/h3&gt;
&lt;p&gt;Before we jump into code, let's get familiar with a few terms:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflows&lt;/strong&gt; are like recipes. They're files that tell GitHub Actions what to do. You write them in a language called YAML (it's just a fancy text format, nothing scary).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Events&lt;/strong&gt; are triggers. &quot;When someone pushes code&quot; or &quot;when someone opens a pull request&quot; - these are events that kick off your workflow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Jobs&lt;/strong&gt; are the big tasks in your recipe. Like &quot;build the app&quot; or &quot;run tests.&quot; Each job runs on its own fresh computer in the cloud.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Steps&lt;/strong&gt; are the small tasks inside a job. Things like &quot;install dependencies&quot; or &quot;run the linter.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Actions&lt;/strong&gt; are pre-made shortcuts. Instead of writing commands to set up Node.js from scratch, you can use an action someone else already made. It's like using a package from npm, but for your workflows.&lt;/p&gt;
&lt;h3 id=&quot;let-s-build-your-first-workflow-&quot;&gt;Let's Build Your First Workflow!&lt;/h3&gt;
&lt;p&gt;Alright, enough theory. Let's make something real. We're going to create a workflow that checks your code every time you push or open a pull request.&lt;/p&gt;
&lt;h4 id=&quot;creating-the-file&quot;&gt;Creating the File&lt;/h4&gt;
&lt;p&gt;First, make a new folder in your project: &lt;code&gt;.github/workflows/&lt;/code&gt;. Yes, it starts with a dot - that's on purpose!&lt;/p&gt;
&lt;p&gt;Inside that folder, create a file called &lt;code&gt;ci.yml&lt;/code&gt;. Here's what goes in it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: CI Pipeline

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

permissions:
  contents: read

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run linter
        run: npm run lint
      
      - name: Type check
        run: npm run type-check
      
      - name: Build project
        run: npm run build
      
      - name: Run tests
        run: npm test
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;what-s-all-this-mean-&quot;&gt;What's All This Mean?&lt;/h4&gt;
&lt;p&gt;Let me walk you through it:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;name: CI Pipeline&lt;/strong&gt; - Just a friendly name for your workflow. You'll see it in the GitHub interface.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;on:&lt;/strong&gt; - This is where you say &quot;run this when...&quot; In our case, we're saying &quot;run when someone pushes to main or develop, OR when someone opens a pull request.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;permissions:&lt;/strong&gt; - This is important! We're telling GitHub &quot;hey, this workflow only needs permission to read our code, nothing else.&quot; It's like giving someone a library card instead of the keys to the building. Good security practice!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;jobs:&lt;/strong&gt; - Here's where the actual work happens. We've got one job called &quot;build-and-test.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;runs-on: ubuntu-latest&lt;/strong&gt; - This means &quot;run on a computer with Ubuntu Linux.&quot; GitHub provides this for free!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;steps:&lt;/strong&gt; - These are the actual commands that run, one after another:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, we grab the code&lt;/li&gt;
&lt;li&gt;Then we install Node.js&lt;/li&gt;
&lt;li&gt;Install our dependencies&lt;/li&gt;
&lt;li&gt;Run the linter to check code style&lt;/li&gt;
&lt;li&gt;Check for TypeScript errors&lt;/li&gt;
&lt;li&gt;Build the project&lt;/li&gt;
&lt;li&gt;Run our tests&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;setting-up-your-package-json&quot;&gt;Setting Up Your package.json&lt;/h4&gt;
&lt;p&gt;Your &lt;code&gt;package.json&lt;/code&gt; needs to have these scripts for the workflow to work:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;vite&quot;,
    &quot;build&quot;: &quot;tsc &amp;amp;&amp;amp; vite build&quot;,
    &quot;lint&quot;: &quot;eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0&quot;,
    &quot;type-check&quot;: &quot;tsc --noEmit&quot;,
    &quot;test&quot;: &quot;vitest run&quot;,
    &quot;preview&quot;: &quot;vite preview&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don't have ESLint or Vitest set up yet? No worries - you can skip those steps in your workflow for now and add them later.&lt;/p&gt;
&lt;h3 id=&quot;want-to-deploy-automatically-too-&quot;&gt;Want to Deploy Automatically Too?&lt;/h3&gt;
&lt;p&gt;Once your tests pass, you might want to deploy your app. Here's a workflow that deploys to GitHub Pages:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Deploy to GitHub Pages

on:
  push:
    branches: [ main ]

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

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build project
        run: npm run build
      
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: ./dist
  
  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This one's a bit different - it has TWO jobs. The second job (deploy) waits for the first one (build) to finish successfully. That &lt;code&gt;needs: build&lt;/code&gt; line is what makes that happen.&lt;/p&gt;
&lt;h3 id=&quot;making-it-faster-with-caching&quot;&gt;Making It Faster with Caching&lt;/h3&gt;
&lt;p&gt;Want your workflows to run faster? Good news - the &lt;code&gt;actions/setup-node@v4&lt;/code&gt; action we're using already caches your npm packages when you add &lt;code&gt;cache: 'npm'&lt;/code&gt;. That means the second time your workflow runs, it won't have to download all your dependencies again. Pretty smart!&lt;/p&gt;
&lt;h3 id=&quot;keeping-secrets-secret&quot;&gt;Keeping Secrets... Secret&lt;/h3&gt;
&lt;p&gt;Sometimes you need API keys or passwords in your builds. DON'T put them directly in your workflow file - that's like posting your house keys on Instagram.&lt;/p&gt;
&lt;p&gt;Instead, use GitHub's secrets feature:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to your repo's Settings&lt;/li&gt;
&lt;li&gt;Click on &quot;Secrets and variables&quot; then &quot;Actions&quot;&lt;/li&gt;
&lt;li&gt;Click &quot;New repository secret&quot;&lt;/li&gt;
&lt;li&gt;Add your secret (like &lt;code&gt;VITE_API_KEY&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then use it in your workflow like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;- name: Build project
  run: npm run build
  env:
    VITE_API_KEY: ${{ secrets.VITE_API_KEY }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GitHub will hide it in the logs automatically. Nice!&lt;/p&gt;
&lt;h3 id=&quot;checking-if-it-s-working&quot;&gt;Checking If It's Working&lt;/h3&gt;
&lt;p&gt;After you push your workflow file to GitHub:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Click the &quot;Actions&quot; tab in your repo&lt;/li&gt;
&lt;li&gt;You'll see your workflow running (or done running)&lt;/li&gt;
&lt;li&gt;Click on it to see all the details&lt;/li&gt;
&lt;li&gt;If something breaks, click on the red X to see what went wrong&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The logs tell you EXACTLY what happened at each step. It's super helpful for debugging.&lt;/p&gt;
&lt;h3 id=&quot;understanding-workflow-permissions&quot;&gt;Understanding Workflow Permissions&lt;/h3&gt;
&lt;p&gt;Quick important thing - see that &lt;code&gt;permissions&lt;/code&gt; block we added? That's like setting up proper door locks for your house. &lt;/p&gt;
&lt;p&gt;By default, workflows might have way more permissions than they need. We're being explicit and saying &quot;this workflow only needs to READ the code&quot; with &lt;code&gt;contents: read&lt;/code&gt;. If later you need to do something like comment on pull requests, you'd add &lt;code&gt;pull-requests: write&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It's just a good habit to be clear about what your workflow can and can't do. Your future self (and your team) will thank you!&lt;/p&gt;
&lt;h3 id=&quot;some-quick-tips&quot;&gt;Some Quick Tips&lt;/h3&gt;
&lt;p&gt;Here are a few things that'll make your life easier:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Set permissions explicitly&lt;/strong&gt; - Like we just talked about, always add that &lt;code&gt;permissions&lt;/code&gt; block. Start with &lt;code&gt;contents: read&lt;/code&gt; and only add more when you need it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use version tags&lt;/strong&gt; - Notice how we use &lt;code&gt;@v4&lt;/code&gt; in our actions? That's better than using &lt;code&gt;@main&lt;/code&gt; because it won't suddenly change on you. Your workflows stay stable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;npm ci&lt;/code&gt; not &lt;code&gt;npm install&lt;/code&gt;&lt;/strong&gt; - In automated environments, &lt;code&gt;npm ci&lt;/code&gt; is faster and more reliable. It's designed specifically for CI/CD.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Run jobs in parallel&lt;/strong&gt; - If you have multiple jobs that don't depend on each other, they'll run at the same time. Saves time!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Add a badge to your README&lt;/strong&gt; - In the Actions tab, you can grab a badge that shows if your build is passing. It looks professional!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test different Node versions&lt;/strong&gt; - You can use something called a matrix to test on Node 18, 20, and 21 all at once. Here's how:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;strategy:
  matrix:
    node-version: [18, 20, 21]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Protect your main branch&lt;/strong&gt; - In your repo settings, you can require that all checks pass before merging. No more broken builds!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Separate your workflows&lt;/strong&gt; - Keep your testing workflow separate from your deployment workflow. Makes things easier to understand and manage.&lt;/p&gt;
&lt;h3 id=&quot;when-things-go-wrong&quot;&gt;When Things Go Wrong&lt;/h3&gt;
&lt;p&gt;Don't panic! Here are common issues and fixes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Workflow not running?&lt;/strong&gt; - Check that your YAML file is in &lt;code&gt;.github/workflows/&lt;/code&gt; and that you don't have any syntax errors. YAML is picky about spaces and indentation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Works on your computer but not in CI?&lt;/strong&gt; - Make sure all your dependencies are listed in &lt;code&gt;package.json&lt;/code&gt;. Also check that you're not relying on environment variables that only exist on your machine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Running out of build minutes?&lt;/strong&gt; - Public repos get unlimited minutes! Private repos have limits. Check your usage in Settings → Billing.&lt;/p&gt;
&lt;h3 id=&quot;what-s-next-&quot;&gt;What's Next?&lt;/h3&gt;
&lt;p&gt;You now know the basics! Here are some cool things to explore when you're ready:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Browse the GitHub Marketplace for actions other people have made&lt;/li&gt;
&lt;li&gt;Set up notifications to Slack or Discord when builds fail&lt;/li&gt;
&lt;li&gt;Add code coverage reporting&lt;/li&gt;
&lt;li&gt;Create custom actions for your specific needs&lt;/li&gt;
&lt;li&gt;Try matrix builds to test across multiple environments&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key is to start simple - get your basic testing pipeline working - then add more features as you need them. Don't try to do everything at once!&lt;/p&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h3&gt;
&lt;p&gt;GitHub Actions might seem a bit intimidating at first, but once you get the hang of it, you'll wonder how you lived without it. No more forgetting to run tests. No more &quot;oops, I broke main.&quot; Just push your code and let your automation buddy do its thing.&lt;/p&gt;
&lt;p&gt;Start with the simple CI workflow we built together, make sure it works, and then gradually add more automation as you get comfortable. Before you know it, you'll be the GitHub Actions expert on your team!&lt;/p&gt;
&lt;p&gt;Happy automating! &lt;/p&gt;
</description>
<category>GitHub Actions</category>
<guid isPermaLink="true">https://coderlegion.com/8878/hello-world-to-github-actions-your-first-automated-workflow</guid>
<pubDate>Thu, 25 Dec 2025 06:23:00 +0000</pubDate>
</item>
<item>
<title>The difference between deployments and releases</title>
<link>https://coderlegion.com/8293/the-difference-between-deployments-and-releases</link>
<description>&lt;p&gt;In software delivery, it’s common to use the terms release and deployment to refer to the same thing: making software available to users. The terms are traditionally synonyms because they happen at the same time. However, modern software delivery practices encourage us to separate the concepts.&lt;/p&gt;
&lt;p&gt;Let’s define the terms with more precise language and look at the practical benefits of the distinction:&lt;/p&gt;
&lt;p&gt;Deployment is when you install a software version on an environment&lt;br&gt;
Release is when you make software available to a user&lt;br&gt;
You could use the word release to describe a package ready for use outside the development team. However, by referring to this as a software version, you can avoid getting into a situation where you release (verb) a release (noun). Instead, you deploy or release a software version.&lt;/p&gt;
&lt;h3 id=&quot;-technical-and-business-tension&quot;&gt; Technical and business tension&lt;/h3&gt;
&lt;p&gt;When DevOps was created, it recognized a natural tension between development and operations. The development team wanted to deploy more often to get new features out, while the operations team wanted to deploy less often to keep the system stable. The goal of DevOps was to resolve this tension.&lt;/p&gt;
&lt;p&gt;If your releases are linked to deployments, you’ll find a similar tension between development and the business. You might have had a situation where the development team wanted to deploy a software version, but the business wanted to hold back the launch for marketing activities or a trade show.&lt;/p&gt;
&lt;p&gt;By decoupling releases from deployments, developers can deploy to production frequently and the business can decide when to launch specific features.&lt;/p&gt;
&lt;h3 id=&quot;a-timeline-for-change&quot;&gt;A timeline for change&lt;/h3&gt;
&lt;p&gt;There’s a 4 step timeline for decoupling releases from deployments in your organization:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adopt the precise terms&lt;/li&gt;
&lt;li&gt;Introduce the architecture&lt;/li&gt;
&lt;li&gt;Start deploying and releasing independently&lt;/li&gt;
&lt;li&gt;Transfer control&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s look at what those steps mean.&lt;/p&gt;
&lt;h4 id=&quot;adopt-the-terms&quot;&gt;Adopt the terms&lt;/h4&gt;
&lt;p&gt;The language change is the easiest place to start. By increasing your precision when talking about deployments and releases, you can start thinking differently about the two different actions.&lt;/p&gt;
&lt;p&gt;You also need to bring your business stakeholders along on this linguistic journey. Having a shared language between the business and technical team members reduces the amount of explanation needed when talking about the software and its deployment pipeline.&lt;/p&gt;
&lt;p&gt;While you coach your organization in semantics, you can start the next step.&lt;/p&gt;
&lt;h4 id=&quot;introduce-the-architecture&quot;&gt;Introduce the architecture&lt;/h4&gt;
&lt;p&gt;There’s a high risk of introducing release controls that become hard to manage. Without a careful design, you could end up with conditional statements scattered through your system or with several competing release control toolsets.&lt;/p&gt;
&lt;p&gt;That’s why your release control toolset is an architectural decision.&lt;/p&gt;
&lt;p&gt;Most release control is done with feature toggles or feature flags.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A feature toggle is a simple on or off switch for a feature&lt;/li&gt;
&lt;li&gt;A feature flag allows selection between different versions of a feature&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can reduce complexity by limiting how many feature controls you have and the states each control has. You should remove release controls by default once the feature’s available to all users. You can identify these regularly and remove them in the next deployment.&lt;/p&gt;
&lt;p&gt;In some cases, you may decide to implement a long-lived release control. For example, a toggle that lets you temporarily remove a resource-heavy feature during a production incident.&lt;/p&gt;
&lt;p&gt;Release controls can apply to all users or groups of users. You should limit the number of groups and the release controls you apply to these groups at one time. You can use group-based release controls for a canary release (where you test out a feature with a small group of early-access users) or for A/B testing two different feature implementations.&lt;/p&gt;
&lt;p&gt;There’s helpful guidance on limiting complexity in &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.martinfowler.com/articles/feature-toggles.html&quot;&gt;this article on feature toggles&lt;/a&gt; by Pete Hodgson on Martin Fowler’s website.&lt;/p&gt;
&lt;p&gt;Your design for release controls should include a level of audit logging. Audit logging has high value when you respond to bug reports or production incidents.&lt;/p&gt;
&lt;p&gt;When you test your software, you should use realistic business configurations rather than trying to test every combination of release controls.&lt;/p&gt;
&lt;h4 id=&quot;release-independently&quot;&gt;Release independently&lt;/h4&gt;
&lt;p&gt;With a release control in place, you can deploy more often with the feature switched off. You can then wait for the business to line up their launch activities and release the feature when they’re ready without needing a deployment.&lt;/p&gt;
&lt;p&gt;You should find the introduction of the release control has resolved the tension between development and the business. However, the business still depends on an engineer to throw the switch. You can take this a step further in the final step.&lt;/p&gt;
&lt;h4 id=&quot;transfer-control&quot;&gt;Transfer control&lt;/h4&gt;
&lt;p&gt;You might decide to keep hold of the release controls while you gain confidence that everything works smoothly. In the longer term, you should make the appropriate controls available to authorized business users.&lt;/p&gt;
&lt;p&gt;When doing this, ensure they only have release controls intended for business use. You don’t want to trigger a critical incident only to find someone accidentally switched off one of your resource-heavy features.&lt;/p&gt;
&lt;p&gt;You should already have an audit log of release control changes. The audit log is even more vital as you extend the group of users who can make changes.&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;You can resolve classic developer and business tensions by separating releases and deployments. You can create a release control toolset that makes it possible to release features at runtime. You must design release control to limit the complexity and prevent conditional statements from appearing all over your code.&lt;/p&gt;
&lt;p&gt;By gradually introducing the concept of independent releases, you can take the business people with you on the journey. They should be able to launch a feature at the push of a button, just like the development team should be able to deploy on demand.&lt;/p&gt;
</description>
<category>CI/CD Pipelines</category>
<guid isPermaLink="true">https://coderlegion.com/8293/the-difference-between-deployments-and-releases</guid>
<pubDate>Mon, 08 Dec 2025 15:20:22 +0000</pubDate>
</item>
<item>
<title>Implementing an End-to-End CI/CD Pipeline for a Java ATM Simulation Application</title>
<link>https://coderlegion.com/8101/implementing-an-end-to-end-ci-cd-pipeline-for-a-java-atm-simulation-application</link>
<description>&lt;p&gt;Abstract&lt;br&gt;
This case study outlines the design and implementation of a complete CI/CD pipeline for a Java ATM Simulation application. The project aimed to strengthen hands-on skills in DevOps automation, continuous integration, code quality analysis, artifact management, and deployment workflows using real-world tools on AWS.&lt;/p&gt;
&lt;p&gt;Project Goals&lt;/p&gt;
&lt;p&gt;Develop a fully automated CI/CD pipeline from code commit to deployment.&lt;/p&gt;
&lt;p&gt;Integrate Jenkins, Maven, SonarQube, Nexus, and Tomcat into a streamlined workflow.&lt;/p&gt;
&lt;p&gt;Deploy services on separate AWS EC2 instances to mimic production infrastructure.&lt;/p&gt;
&lt;p&gt;Implement artifact versioning and reliable storage.&lt;/p&gt;
&lt;p&gt;Enable automated deployment to an application server with minimal manual intervention.&lt;/p&gt;
&lt;p&gt;Tools &amp;amp; Technology Stack&lt;/p&gt;
&lt;p&gt;AWS EC2: Dedicated servers for Jenkins, SonarQube, Nexus, and Tomcat.&lt;/p&gt;
&lt;p&gt;Jenkins: CI server and pipeline orchestration.&lt;/p&gt;
&lt;p&gt;Maven: Build automation and dependency management.&lt;/p&gt;
&lt;p&gt;SonarQube: Static code analysis and code quality reporting.&lt;/p&gt;
&lt;p&gt;Nexus Repository Manager: Artifact storage and version control.&lt;/p&gt;
&lt;p&gt;Apache Tomcat: Application server deployment.&lt;/p&gt;
&lt;p&gt;GitHub: Source code management.&lt;/p&gt;
&lt;p&gt;Java 11+: Application and tooling requirement.&lt;/p&gt;
&lt;p&gt;Jenkins Plugins: Git, Maven, SSH Agent, SonarQube Scanner.&lt;/p&gt;
&lt;p&gt;Infrastructure Setup&lt;br&gt;
Four EC2 instances were provisioned, each hosting a separate tool to simulate production:&lt;/p&gt;
&lt;p&gt;Jenkins on port 8080&lt;/p&gt;
&lt;p&gt;SonarQube on port 9000&lt;/p&gt;
&lt;p&gt;Nexus on port 8081&lt;/p&gt;
&lt;p&gt;Tomcat on port 8080&lt;/p&gt;
&lt;p&gt;Java was installed on all instances, and security groups were configured for inter-tool communication. Each server was validated for accessibility via a browser to ensure proper connectivity.&lt;/p&gt;
&lt;p&gt;Pipeline Design &amp;amp; Implementation&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Source Control Integration&lt;br&gt;
The ATM Simulation project was pushed to GitHub. Jenkins was configured to clone the repository automatically on each build trigger using a declarative pipeline script.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol&gt;
&lt;li&gt;Build &amp;amp; Test Automation&lt;br&gt;
mvn clean package was used to compile code, execute tests, and generate artifacts. Maven ensured dependency management and consistent builds across environments.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol&gt;
&lt;li&gt;Code Quality Analysis&lt;br&gt;
SonarQube was integrated by updating pom.xml and Jenkins credentials. Running mvn sonar:sonar generated detailed code metrics, highlighting vulnerabilities and enforcing coding standards.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol&gt;
&lt;li&gt;Artifact Management&lt;br&gt;
A Maven repository was created in Nexus. pom.xml and Jenkins settings.xml were configured to upload artifacts via mvn deploy, ensuring version-controlled storage and rollback capability.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol&gt;
&lt;li&gt;Automated Deployment to Tomcat&lt;br&gt;
Tomcat manager roles and credentials were configured. Jenkins deployed the final WAR file, completing the continuous delivery process. Poll SCM triggers enabled automation for every commit.&lt;/li&gt;
&lt;/ol&gt;
&lt;ol&gt;
&lt;li&gt;Advanced Jenkins Configuration&lt;br&gt;
The SSH Agent plugin simulated a master-slave architecture, enabling distributed builds and enhancing pipeline scalability.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Challenges &amp;amp; Solutions&lt;/p&gt;
&lt;p&gt;Tool Version Conflicts: Resolved Java compatibility issues for SonarQube and Nexus.&lt;/p&gt;
&lt;p&gt;Nexus Deployment Failures: Fixed misconfigurations in pom.xml and settings.xml.&lt;/p&gt;
&lt;p&gt;Tomcat Deployment Errors: Corrected manager roles and permissions for successful deployment.&lt;/p&gt;
&lt;p&gt;Pipeline Failures: Updated Jenkins plugins and validated pipeline scripts to resolve build issues.&lt;/p&gt;
&lt;p&gt;Key Learnings&lt;/p&gt;
&lt;p&gt;Hands-on experience in end-to-end CI/CD workflows.&lt;/p&gt;
&lt;p&gt;Integration between source control, CI servers, artifact repositories, and application servers.&lt;/p&gt;
&lt;p&gt;Best practices in artifact versioning, storage, and automated deployment.&lt;/p&gt;
&lt;p&gt;Troubleshooting pipeline failures and ensuring tool compatibility.&lt;/p&gt;
&lt;p&gt;Leveraging Jenkins plugins for scalable master-slave architectures.&lt;/p&gt;
&lt;p&gt;Real-world automation of software delivery and cloud deployment.&lt;/p&gt;
&lt;p&gt;Conclusion&lt;br&gt;
This project provided practical exposure to enterprise-grade DevOps practices, including automation, CI/CD pipeline creation, artifact management, and deployment orchestration. It strengthened problem-solving, tool integration, and cloud deployment skills, demonstrating the ability to implement and manage robust DevOps workflows in real-world environments while reinforcing a commitment to continuous learning in automation and CI/CD.&lt;/p&gt;
</description>
<category>CI/CD Pipelines</category>
<guid isPermaLink="true">https://coderlegion.com/8101/implementing-an-end-to-end-ci-cd-pipeline-for-a-java-atm-simulation-application</guid>
<pubDate>Tue, 02 Dec 2025 23:48:32 +0000</pubDate>
</item>
<item>
<title>Deploy your application on Vercel with GitHub Actions</title>
<link>https://coderlegion.com/4878/deploy-your-application-on-vercel-with-github-actions</link>
<description>&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=5202709586993786117&quot; alt=&quot;&quot;&gt;&lt;br&gt;
A very common CI/CD pattern for frontend apps is: &lt;strong&gt;build in CI and deploy to production automatically when the code reaches&lt;/strong&gt; &lt;code&gt;main&lt;/code&gt;.&lt;br&gt;
If you use Vercel, you can do this with a &lt;strong&gt;short and reliable&lt;/strong&gt; GitHub Actions workflow that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;downloads your project's configuration and variables from Vercel,&lt;/li&gt;
&lt;li&gt;builds the app in CI,&lt;/li&gt;
&lt;li&gt;and deploys the already built artifacts to &lt;strong&gt;production&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this article, we'll walk through it step by step and I'll give you some tips to help you understand &lt;strong&gt;what each line does&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;purpose-of-the-workflow&quot;&gt;Purpose of the workflow&lt;/h4&gt;
&lt;p&gt;Every time there is a push to &lt;code&gt;main&lt;/code&gt;, the following should be executed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pull config&lt;/strong&gt; from Vercel (includes &lt;strong&gt;production&lt;/strong&gt; environment variables).&lt;/li&gt;
&lt;li&gt;Reproducible &lt;strong&gt;build&lt;/strong&gt; in CI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy&lt;/strong&gt; that artifact (without rebuilding in Vercel).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This will give you &lt;strong&gt;predictable&lt;/strong&gt; deployments that are easy to audit and debug.&lt;/p&gt;
&lt;h4 id=&quot;general-workflow-structure&quot;&gt;General workflow structure&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Vercel PRO deployment
on:
  push:
    branches:
      - main
jobs:
  deploy:
    name: deploy
    runs-on: ubuntu-latest
    steps:
      - id: checkout-step
        name: Checkout code
        uses: actions/checkout@v4
      - id: install-vercel-cli-step
        name: Install Vercel CLI
        run: npm install --global vercel@latest
      - id: pull-vercel-env-info-step
        name: Pull Vercel Environment Information
        run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN
          }}
      - id: build-vercel-artifacts-step
        name: Build Project Artifacts
        run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
      - id: deploy-vercel-artifacts-step
        name: Deploy Project Artifacts to Vercel
        run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;&lt;br&gt;
Create the secret &lt;code&gt;VERCEL_TOKEN&lt;/code&gt; in your repository (Settings → Secrets and variables → Actions).&lt;br&gt;
Either commit &lt;code&gt;.vercel/project.json&lt;/code&gt;, or provide &lt;code&gt;VERCEL_ORG_ID&lt;/code&gt; and &lt;code&gt;VERCEL_PROJECT_ID&lt;/code&gt; as secrets when you do vercel pull.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;step-by-step&quot;&gt;Step by step&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;on: push (to main)&lt;/strong&gt;&lt;br&gt;
Each push to &lt;code&gt;main&lt;/code&gt; triggers the production pipeline. If you prefer to only deploy when merging by PR, simply avoid pushing directly to &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;uses: actions/checkout@v4&lt;/strong&gt;&lt;br&gt;
Download your code to the runner so that the following tools (Vercel CLI) have it available.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;npm install --global vercel@latest&lt;/strong&gt;&lt;br&gt;
Install the Vercel CLI.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; to avoid surprises, you can set version: vercel@xx (or whatever you use on your team).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;vercel pull --yes --environment=production&lt;/strong&gt;&lt;br&gt;
Bring the project configuration and production environment variables to &lt;code&gt;.vercel/&lt;/code&gt; on the runner.&lt;br&gt;
In CI there are no interactive questions, so we use &lt;code&gt;--yes&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.vercel/project.json&lt;/code&gt; in the repository or&lt;/li&gt;
&lt;li&gt;&lt;code&gt;VERCEL_ORG_ID&lt;/code&gt; and &lt;code&gt;VERCEL_PROJECT_ID&lt;/code&gt; as variables/secrets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;vercel build --prod&lt;/strong&gt;&lt;br&gt;
Builds in CI using your framework (Next.js, SvelteKit, etc.) or what is defined in &lt;code&gt;vercel.json&lt;/code&gt;.&lt;br&gt;
The result is stored in &lt;code&gt;.vercel/output&lt;/code&gt; and is exactly what you are going to deploy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;vercel deploy --prebuilt --prod&lt;/strong&gt;&lt;br&gt;
Uploads the already built artifact to production.&lt;br&gt;
With &lt;code&gt;--prebuilt&lt;/code&gt;, Vercel does not rebuild: what you tested in CI is what your user will see.&lt;/p&gt;
&lt;h4 id=&quot;-final-result&quot;&gt;✅ Final result&lt;/h4&gt;
&lt;p&gt;With this workflow, each merge to &lt;code&gt;main&lt;/code&gt; runs a clean build and deploys that same artifact to production in Vercel. You get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reproducible builds,&lt;/li&gt;
&lt;li&gt;faster deploys,&lt;/li&gt;
&lt;li&gt;and clear CI logs for debugging.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;prefer-to-configure-it-visually-&quot;&gt;Prefer to configure it visually?&lt;/h4&gt;
&lt;p&gt;Writing workflows by hand gives you total control, but sometimes you want speed with built-in validations.&lt;/p&gt;
&lt;p&gt;With &lt;strong&gt;OctoLab&lt;/strong&gt;, you can set up this same pipeline visually: choose actions, fill in fields, and the YAML is generated instantly, ready to commit.&lt;/p&gt;
&lt;p&gt;Try it out: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.octolab.app/templates/vercel-pro-deployment&quot;&gt;https://www.octolab.app/templates/vercel-pro-deployment&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;conclusion-2&quot;&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;This workflow provides you with a clean &lt;strong&gt;CI → production&lt;/strong&gt; flow on every push to &lt;code&gt;main&lt;/code&gt;, using Vercel's recommended approach: &lt;strong&gt;pull → build → deploy&lt;/strong&gt;. It's easy to adapt to previews, monorepos, or strict reproducibility (fixed Node and CLI). &lt;/p&gt;
&lt;p&gt;Use it as a base and evolve it with caching, notifications, or environment gates as your team grows.&lt;/p&gt;
</description>
<category>Getting Started</category>
<guid isPermaLink="true">https://coderlegion.com/4878/deploy-your-application-on-vercel-with-github-actions</guid>
<pubDate>Wed, 20 Aug 2025 17:26:29 +0000</pubDate>
</item>
<item>
<title>Publish your packages to NPM automatically with GitHub Actions</title>
<link>https://coderlegion.com/4825/publish-your-packages-to-npm-automatically-with-github-actions</link>
<description>&lt;blockquote&gt;&lt;p&gt;Automating publication prevents manual errors, speeds up releases, and forces you to maintain a repeatable and transparent process.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In this article, we're going to create a GitHub Actions workflow that &lt;strong&gt;publishes to NPM&lt;/strong&gt; when you push to &lt;code&gt;main&lt;/code&gt;. The flow installs dependencies, runs tests, compiles, and, if all goes well, &lt;strong&gt;publishes&lt;/strong&gt; the package using a secure token stored as a secret.&lt;/p&gt;
&lt;h4 id=&quot;what-is-the-purpose-of-the-workflow-&quot;&gt;What is the purpose of the workflow?&lt;/h4&gt;
&lt;p&gt;Every time you merge changes to &lt;code&gt;main&lt;/code&gt;, we want to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install dependencies&lt;/li&gt;
&lt;li&gt;Run tests&lt;/li&gt;
&lt;li&gt;Build the package&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Publish&lt;/strong&gt; to NPM using secure credentials&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This standardizes the publishing process and reduces the risk of human error.&lt;/p&gt;
&lt;h4 id=&quot;general-workflow-structure-1&quot;&gt;General workflow structure&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;name: Publish to NPM
on:
  push:
    branches:
      - main

jobs:
  publish:
    name: publish
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: &quot;22&quot;

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm run test

      - name: Build package
        run: npm run build

      - name: Publish to NPM
        uses: JS-DevTools/npm-publish@v3
        with:
          token: ${{ secrets.NPM_TOKEN }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;-what-does-each-step-do-&quot;&gt;️ ️ What does each step do?&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;on: push to main&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Triggers the workflow when pushing to the &lt;code&gt;main&lt;/code&gt; branch. If you prefer to publish only with tags, you can change the trigger to &lt;code&gt;on: push: tags: - ‘v*’&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;actions/checkout@v4&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Downloads the repository to the runner.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;actions/setup-node@v4&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Configures Node.js (v22 in the example). You can enable &lt;code&gt;cache: ‘npm’&lt;/code&gt; to speed up installations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;npm ci / npm install&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Installs dependencies. &lt;code&gt;npm ci&lt;/code&gt; is faster and more reproducible in CI if you use &lt;code&gt;package-lock.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;npm run test and npm run build&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Verify that the package passes tests and compiles correctly before publishing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;JS-DevTools/npm-publish@v3&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Publish the package by reading &lt;code&gt;package.json&lt;/code&gt;. Use the token you pass through &lt;code&gt;with.token&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;authentication-with-npm-npm-token&quot;&gt;Authentication with NPM: NPM_TOKEN&lt;/h4&gt;
&lt;p&gt;To publish, you need a token in &lt;strong&gt;NPM&lt;/strong&gt; and save it as a secret in GitHub:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an &lt;strong&gt;Automation Token&lt;/strong&gt; in your NPM account (recommended for CI; respects 2FA and allows automatic publishing).&lt;/li&gt;
&lt;li&gt;In GitHub, go to &lt;strong&gt;Settings&lt;/strong&gt; → &lt;strong&gt;Secrets and variables&lt;/strong&gt; → &lt;strong&gt;Actions&lt;/strong&gt; → &lt;strong&gt;New repository secret&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Create the secret &lt;code&gt;NPM_TOKEN&lt;/code&gt; with the value of the NPM token.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;  If your package is scoped (e.g., &lt;code&gt;@your-org/package&lt;/code&gt;), make sure that &lt;code&gt;package.json&lt;/code&gt; contains &lt;code&gt;“name”: “@your-org/package”&lt;/code&gt; and &lt;code&gt;“publishConfig”: { ‘access’: “public” }&lt;/code&gt; if you want it to be public.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Example of &lt;code&gt;publishConfig&lt;/code&gt; in &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;@tu-org/tu-paquete&quot;,
  &quot;version&quot;: &quot;1.2.3&quot;,
  &quot;publishConfig&quot;: {
    &quot;access&quot;: &quot;public&quot;,
    &quot;tag&quot;: &quot;latest&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;dry-run-and-version-control&quot;&gt;Dry-run and version control&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Dry-run:&lt;/strong&gt; test the flow without actually publishing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Publish to NPM (dry run)
  uses: JS-DevTools/npm-publish@v3
  with:
    token: ${{ secrets.NPM_TOKEN }}
    dry-run: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Versioning:&lt;/strong&gt; this flow publishes the version specified in &lt;code&gt;package.json&lt;/code&gt;. Be sure to &lt;strong&gt;update the version&lt;/strong&gt; before merging to &lt;code&gt;main&lt;/code&gt;.&lt;br&gt;
If you use automatic versioning (e.g., Conventional Commits + automatic releases), integrate that stage &lt;strong&gt;before&lt;/strong&gt; the publication step.&lt;/p&gt;
&lt;h4 id=&quot;-final-result-1&quot;&gt;✅ Final result&lt;/h4&gt;
&lt;p&gt;With this workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You publish to NPM in a &lt;strong&gt;consistent and repeatable&lt;/strong&gt; manner.&lt;/li&gt;
&lt;li&gt;You avoid broken releases thanks to &lt;strong&gt;preliminary tests + builds&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You keep credentials secure with &lt;strong&gt;GitHub Secrets&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You reduce manual steps and time between merge and release.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;would-you-rather-set-it-up-with-a-visual-interface-&quot;&gt;Would you rather set it up with a visual interface?&lt;/h4&gt;
&lt;p&gt;If you're interested in defining this flow with a visual interface and getting the YAML instantly, you can use &lt;strong&gt;OctoLab&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Step-by-step visual editor&lt;/li&gt;
&lt;li&gt;Dynamic fields for actions and tokens&lt;/li&gt;
&lt;li&gt;Real-time YAML preview&lt;/li&gt;
&lt;li&gt;Built-in validations&lt;/li&gt;
&lt;li&gt;Copy/download and you're done&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt; Try it out: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.octolab.app/templates/npm-publish&quot;&gt;https://www.octolab.app/templates/npm-publish&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;conclusion-3&quot;&gt;Conclusion&lt;/h4&gt;
&lt;p&gt;Automating publication to NPM with GitHub Actions reduces friction and errors. With a simple configuration—tests, build, and a publish step with a secure token—you can standardize releases and focus on building value.&lt;/p&gt;
&lt;p&gt;If this was helpful, let me know what you would like to add to the template (tags, prereleases, monorepos, changelogs, etc.). Let's keep going!&lt;/p&gt;
</description>
<category>GitHub Actions</category>
<guid isPermaLink="true">https://coderlegion.com/4825/publish-your-packages-to-npm-automatically-with-github-actions</guid>
<pubDate>Tue, 19 Aug 2025 11:21:15 +0000</pubDate>
</item>
<item>
<title>Beyond skeleton pipelines: who owns your software pipeline?</title>
<link>https://coderlegion.com/4708/beyond-skeleton-pipelines-who-owns-your-software-pipeline</link>
<description>&lt;p&gt;Your current software delivery processes are probably working fine. They build your code, maybe run a test or two, and get your software to production. That's no small achievement, but here's a question that might make you pause.&lt;/p&gt;
&lt;p&gt;When did someone last spend time improving them?&lt;/p&gt;
&lt;p&gt;If you're like many software teams I've worked with, the answer is probably &quot;not recently&quot;. Maybe not ever.&lt;/p&gt;
&lt;p&gt;I've worked with developer teams at different maturity levels and have repeatedly seen this pattern. Great development teams who are busy building software but aren't large enough to have platform teams or dedicated operational folk responsible for the software delivery process. They typically start with a template from their cloud provider, delivery tooling vendor, or, these days, their favorite AI LLM. Initially, it gets the job done, but I refer to these as skeleton pipelines—the bare minimum framework to ship code.&lt;/p&gt;
&lt;p&gt;Skeleton pipelines are a great starting point. They get you from nothing to shipping code quickly, but your pipelines are living, breathing artifacts. Before long, they need some meat added to the skeleton to provide functionality beyond the basics and make your delivery process robust and delightful.&lt;/p&gt;
&lt;p&gt;I've seen when teams experience friction with their software release process, but don't treat it as a bug or feature improvement that needs to be logged, prioritized, and worked on. If your deployment pipelines are responsible for delivering your product to customers, shouldn't you treat them as part of your product? When was the last time someone added a pipeline improvement to your backlog or spent time making your deployment process better?&lt;/p&gt;
&lt;p&gt;The important shift is treating delivery process improvements as regular development work, not spare-time projects that inevitably get pushed aside.&lt;/p&gt;
&lt;h3 id=&quot;the-ownership-vacuum&quot;&gt;The ownership vacuum&lt;/h3&gt;
&lt;p&gt;Ask any group of engineers, &quot;Who owns your software delivery pipelines?&quot; and I bet you'll get different answers. When writing this post, I put that question into a search engine and reviewed the discussions, which weren't surprising. Some say developers should own what they build, others insist it's up to platform or DevOps engineers. Then there's the view that everyone is responsible because DevOps is about everyone working together. My observation is that often, no one owns it, and that's when it becomes a problem.&lt;/p&gt;
&lt;p&gt;The distinction that matters isn't really about ownership. It's about accountability. Someone needs to be accountable for ensuring your delivery process evolves, improves, and supports your organization with shipping software rather than being a friction-filled process people loathe working with. Without that accountability, pipelines can easily become unmanaged components that gradually deteriorate until they become technical debt and a bottleneck instead of an enabler.&lt;/p&gt;
&lt;p&gt;There's no one-size-fits-all answer for who should own and be accountable for your pipelines. You must discuss and agree based on your maturity, size, team structure, and individual skill sets. The important thing is that it is discussed and agreed upon, and not left to assumptions. Ask yourself now if everyone on the team knows how and where to raise an issue with the deployment process today. If the answer is no, then start there. Determine who's responsible and establish clear channels for pipeline feedback, problems, and improvements.&lt;/p&gt;
&lt;h3 id=&quot;the-cost-of-skeleton-pipelines&quot;&gt;The cost of skeleton pipelines&lt;/h3&gt;
&lt;p&gt;This ownership vacuum creates predictable problems I've seen many times. Teams fall into the &quot;if it ain't broke, don't fix it&quot; mentality, leaving pipelines as-is for months or years. The issue is that what worked well 6, 12, or 18 months ago likely isn't fit for purpose today. New tooling and techniques exist now that didn't then. Your team will likely ship more frequently—or at least want to—and handle more complex deployments than when you first created that pipeline. The infrastructure you're deploying to has likely changed. Different developers and engineers might work at your company who didn't before. You're missing out on tooling and practices that could: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Save weekly hours&lt;/li&gt;
&lt;li&gt;Bring new functionality to your software delivery lifecycle that adds business value&lt;/li&gt;
&lt;li&gt;Catch issues before they become customer problems&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Worse, they often become single points of knowledge. Whoever initially sets up the pipeline is the only person who understands how it works. When that person leaves or gets pulled onto other projects, your team inherits a mysterious system everyone fears touching.&lt;/p&gt;
&lt;p&gt;Fragile pipelines break at the worst possible moments. They miss opportunities to catch issues early, optimize build times, have predictable outcomes, or provide better feedback to developers. Maybe, most importantly, they frustrate your team and slow down what they're supposed to enable: shipping great software and products.&lt;/p&gt;
&lt;h3 id=&quot;you-can-t-afford-to-ignore-this&quot;&gt;You can't afford to ignore this&lt;/h3&gt;
&lt;p&gt;&quot;We're too busy building features to worry about pipeline improvements.&quot; Sound familiar? It's a common pushback I hear from teams, and I get it. When trying to ship products and keep customers happy, spending time on what feels like an internal process is often considered a luxury you can't afford.&lt;/p&gt;
&lt;p&gt;Platform teams are getting a lot of attention lately, and for good reason, as they work well for larger organizations. But if your company doesn't need everything else a platform team typically handles, creating one just for pipeline ownership is unnecessary. The reality is that you don't need a dedicated platform team to own your pipelines. You can borrow their mindset by treating your delivery process as part of your product. Pipeline improvements get backlog items, performance issues get bug tickets, and deployment pain points get the same attention as user-reported problems.&lt;/p&gt;
&lt;p&gt;Start by paying attention to where your process hurts. If deployments make you nervous, your team loathes &quot;release day&quot;, when releases fail for mysterious reasons or when you're manually testing things your team can automate. These pain points are your roadmap for high-value improvements.&lt;/p&gt;
&lt;h3 id=&quot;making-the-investment&quot;&gt;Making the investment&lt;/h3&gt;
&lt;p&gt;Improvements may include: &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding vulnerability scanning during builds&lt;/li&gt;
&lt;li&gt;Configuring integration tests against ephemeral infrastructure&lt;/li&gt;
&lt;li&gt;Properly storing and versioning your deployment artifacts&lt;/li&gt;
&lt;li&gt;Implementing flexible approval workflows&lt;/li&gt;
&lt;li&gt;Creating a robust deployment strategy&lt;/li&gt;
&lt;li&gt;Measuring deployment metrics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm not giving you a shopping list of things to do. Instead, I recognize that &quot;you don't know what you don't know&quot; because I've been there too, and you may be experiencing issues you didn't know there were solutions to.&lt;/p&gt;
&lt;p&gt;We have a white paper titled &lt;a rel=&quot;nofollow&quot; href=&quot;https://octopus.com/whitepapers/ten-pillars-of-pragmatic-deployments&quot;&gt;The ten pillars of pragmatic deployments&lt;/a&gt;. I bring this to your attention not for a checklist of everything you should do but for insight into what we think good deployments look like based on years of experience building a deployment automation tool. The ideas discussed in this post might help you enhance your software delivery process today or provide insight into issues and solutions you may not have been aware of.&lt;/p&gt;
&lt;h3 id=&quot;take-ownership&quot;&gt;Take ownership&lt;/h3&gt;
&lt;p&gt;It's easy to overlook software delivery pipelines. They're usually out of sight until they get in your way, kind of like plumbing. It's invisible when it works and catastrophic when it doesn't. Excellent plumbing supports everything in your house to function smoothly, and great delivery processes do the same for your product development.&lt;/p&gt;
&lt;p&gt;Claim ownership of your software delivery processes and ensure someone is accountable for their care and feeding. Talk to your engineers involved in developing and releasing software, and find out where the pain is. Implement easy-to-use feedback loops, add pipeline improvements to your backlog, and treat deployment pain points as bugs worth investing in and fixing.&lt;/p&gt;
&lt;p&gt;Your future self and team will thank you when your next deployment goes smoothly instead of keeping everyone up after hours or nervously huddling around a few pizzas doing your monthly release with crossed fingers.&lt;/p&gt;
</description>
<category>Java</category>
<guid isPermaLink="true">https://coderlegion.com/4708/beyond-skeleton-pipelines-who-owns-your-software-pipeline</guid>
<pubDate>Thu, 14 Aug 2025 00:53:24 +0000</pubDate>
</item>
<item>
<title>Go Ahead! Deploy on Friday!</title>
<link>https://coderlegion.com/4583/go-ahead-deploy-on-friday</link>
<description>&lt;p&gt;&lt;strong&gt;Improving your deployment pipeline reduces the risk of failed deployments overall rather than choosing which day will be a bad day.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Avoiding Friday deployments is driven by a goal that, while admirable, is better achieved in other ways. Yet, organizations are more likely to deploy on Friday than on a Monday, which shows the industry doesn’t buy the myth that you shouldn’t deploy on Friday.&lt;/p&gt;
&lt;h3 id=&quot;-few-practices-are-truly-best-practices&quot;&gt; Few Practices Are Truly Best Practices&lt;/h3&gt;
&lt;p&gt;While you used to hear about best practices all the time, you’ll have noticed that industry experts have become increasingly cautious about the term. Best practices do exist, such as using version control instead of a network share to store your source code. Many other practices turn out to be simply good, rather than best.&lt;/p&gt;
&lt;p&gt;The idea of best practices is that they are table stakes for software delivery, and working without them can be harmful. Good practices, on the other hand, are options. You don’t have to adopt every good practice, as you can cover risk areas with a carefully selected option from the menu of all tried-and-tested ways to solve a problem.&lt;/p&gt;
&lt;p&gt;If your end users are reporting escaped bugs, you can apply one or more testing and monitoring good practices to catch similar issues before users experience them. You look at different testing and monitoring approaches and try one to see its effectiveness. The problem could be solved after you adopt one practice, or it might take several complementary approaches to improve the situation.&lt;/p&gt;
&lt;p&gt;Crucially, you should stop adding practices once you’ve solved the problem. Adding further practices that aren’t required results in unnecessary complexity, which can be harmful to software delivery.&lt;/p&gt;
&lt;p&gt;Finally, there are practices that are actively harmful when pursued. These are often well-intentioned and almost always presented as best practices, but empirical evidence and rigorous research have proven them to be false. Examples of these harmful practices include Gitflow, working in large batches, and heavyweight change-approval processes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=7540102837855853596&quot; alt=&quot;The difference between best practice, good practice, unnecessary complexity, and anti-patterns&quot;&gt;&lt;/p&gt;
&lt;p&gt;A good heuristic for spotting an anti-pattern is that you’ve being told something is a best practice, but it doesn’t sound like table-stakes basic software delivery. If there’s any doubt, look for trusted advice from someone with plenty of experience and cross-reference dependable research, such as the Accelerate &lt;a rel=&quot;nofollow&quot; href=&quot;https://dora.dev/research&quot;&gt;State of DevOps Report&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;never-deploy-on-a-friday&quot;&gt;Never Deploy on a Friday&lt;/h3&gt;
&lt;p&gt;With the practices primer firmly in mind, let’s consider whether we should deploy on Friday. After the recent &lt;a rel=&quot;nofollow&quot; href=&quot;https://thenewstack.io/7-urgent-lessons-from-the-crowdstrike-disaster/&quot;&gt;CrowdStrike outage&lt;/a&gt;, many people suggested things could have been improved if they hadn’t deployed on Friday.&lt;/p&gt;
&lt;p&gt;It’s hard to understand how deploying on Thursday would have improved the CrowdStrike issue. Do we not fly planes or use computers on Thursday? We can explore this confusing claim by looking at the perceived benefits of banning Friday deployments.&lt;/p&gt;
&lt;h3 id=&quot;why-you-should-avoid-friday-deployments&quot;&gt;Why You Should Avoid Friday Deployments&lt;/h3&gt;
&lt;p&gt;I asked folks why we should avoid Friday deployments to see if there was some merit to the claims. As someone whose mind was changed on tabs vs. spaces by solid arguments, I believed I could be convinced given a compelling argument.&lt;/p&gt;
&lt;p&gt;The most common argument for a Friday moratorium was that it would allow developers to spend the weekend with their families instead of working to restore services.&lt;/p&gt;
&lt;p&gt;This is only true if your time to recover from a failed deployment is almost exactly a day. If it takes more than a day, you’ll be working on Saturday to recover from your Thursday deployment, unless you also stop deployments on Thursday. If you can recover in less than a day, you could deploy on Friday and still get to the beach on Saturday.&lt;/p&gt;
&lt;p&gt;The longer your recovery times, the further in the past you need to deploy to avoid working on the weekend. For example, if it takes you a week to recover from a failed deployment, you need to deploy last Friday to avoid working this weekend, though this does mean you have to work last weekend.&lt;/p&gt;
&lt;p&gt;The reality is that recovery times aren’t constant. Some problems are easier to recover from than others. Having a distribution of recovery times means you are taking a probabilistic approach to protecting the weekend. If you deploy every day, you’ll discover that Friday deployments have a higher probability of disrupting the weekend than Monday deployments.&lt;/p&gt;
&lt;p&gt;When you take this approach, you’ll find there are plenty of improvements you can make to your deployment pipeline that reduce the probability of disruption. Banning deployments doesn’t reduce your overall change failure rate, it just means your Monday deployments will fail more often. Improving your deployment pipeline reduces the risk of failed deployments overall, which is far more positive than simply choosing which day will be a bad day.&lt;/p&gt;
&lt;p&gt;Work-life balance applies to Monday night just as much as it applies to the weekend. People have personal commitments every day of the week, from school runs to birthdays and anniversaries to arrangements made to meet an old friend. Each of these is important, so making improvements that reduce failed deployments protects work-life balance more than shifting them to a different workday.&lt;/p&gt;
&lt;h3 id=&quot;real-reasons-to-plan-deployments&quot;&gt;Real Reasons To Plan Deployments&lt;/h3&gt;
&lt;p&gt;There are reasons to avoid deploying at specific times, but they are related to your industry. For example, in the retail industry, you avoid updating the software on tills when the store is open. You don’t need a new version of software while you’re checking out a customer and have a queue of shoppers waiting.&lt;/p&gt;
&lt;p&gt;Your release plan would set out the times to deploy and the times to avoid. It would also capture the process for training retail store staff so they know what’s changed before they raise the shutters.&lt;/p&gt;
&lt;p&gt;By thinking of the people affected by our software, we consider many more people than if we just think of ourselves. If we think of all the people depending on our software, we can consider the work-life balance of a thousand times more people. Ideally, we’d never have a failed deployment, and our rollout would have no downtime. In reality, things do sometimes go wrong, and having a strong recovery time can minimize the impact.&lt;/p&gt;
&lt;p&gt;The research suggests there are capabilities that let you deploy more often while also reducing the failure rate. The top performers are deploying many times a day, including Fridays.&lt;/p&gt;
&lt;h3 id=&quot;deploying-on-friday-is-commonplace&quot;&gt;Deploying on Friday Is Commonplace&lt;/h3&gt;
&lt;p&gt;To understand whether the Friday deployment ban was affecting real organizations, I analyzed over 32 million deployments to see when they were taking place. The data cuts across all organization sizes and many industries. It turns out that deploying on Friday is more common than deploying on Monday.&lt;/p&gt;
&lt;p&gt;This is great news, as it demonstrates that the myth of avoiding Fridays has little traction in practice.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=18316847278261382988&quot; alt=&quot;People deploy on Friday more than they deploy on Monday&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;decide-for-yourself-but-don-t-propagate-the-myth&quot;&gt;Decide for Yourself but Don’t Propagate the Myth&lt;/h3&gt;
&lt;p&gt;It’s perfectly acceptable for your organization to decide they don’t want to deploy on Fridays, though some curiosity about why will either turn up interesting domain knowledge or highlight some things you can improve in your deployment pipeline. What’s less appealing is broadcasting “don’t deploy on Fridays” as a best practice.&lt;/p&gt;
&lt;p&gt;Working in small batches is known to improve software delivery performance. If you are following continuous delivery and DevOps, you might be deploying five times a day. If you rule out Fridays, you build up a Monday deployment that’s five times bigger than your normal batch size. You are opting to downgrade your performance from “on demand” to “daily.”&lt;/p&gt;
&lt;p&gt;The more continuously you deploy, the worse the idea of stopping for a day becomes. That means that the members the “don’t deploy on Friday” movement, in the absence of specific organizational context, are opting for mediocrity.&lt;/p&gt;
&lt;p&gt;Don’t just start deploying on Fridays. Take this as an opportunity to assess your situation, understand users and customers, and start improving your software delivery capabilities.&lt;/p&gt;
</description>
<category>CI/CD Pipelines</category>
<guid isPermaLink="true">https://coderlegion.com/4583/go-ahead-deploy-on-friday</guid>
<pubDate>Mon, 11 Aug 2025 13:12:30 +0000</pubDate>
</item>
<item>
<title>CI/CD Uses</title>
<link>https://coderlegion.com/4444/ci-cd-uses</link>
<description>&lt;p&gt;As I enter the new era of development. CI/CD is a must-have for modern engineers. It improves code quality and saves time. Starting a simple, automate things, and expand.&lt;/p&gt;
&lt;h2 id=&quot;what-is-ci-cd--1&quot;&gt;What is CI/CD?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;CI/CD&lt;/strong&gt; stands for&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CI (Continuous Integration): Automatically test the code when changes are deployed.&lt;/li&gt;
&lt;li&gt;CD (Continuous Development/Delivery): Automatically build and deploy the app for testing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;tools-can-be-use&quot;&gt;Tools can be use&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;GitHub - hosting repo and run Actions&lt;/li&gt;
&lt;li&gt;VS code - to write code and create workflow files.&lt;/li&gt;
&lt;li&gt;Git - to track changes and push to GitHub.&lt;/li&gt;
&lt;li&gt;Vercel - This is already integrating with Git repositories.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;step-by-step-1&quot;&gt;Step-by-Step&lt;/h3&gt;
&lt;p&gt;My step-by-step guide when I am making a new repository and personal projects.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a GitHub repository&lt;ul&gt;
&lt;li&gt;Click &lt;strong&gt;new&lt;/strong&gt; -&amp;gt; Name of the repo -&amp;gt; Add &lt;strong&gt;README.md&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Clone the repository:&lt;pre&gt;&lt;code&gt;  git clone https://github.com/username/repository.git
  cd repository
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;setup-the-project&quot;&gt;Setup the project&lt;/h3&gt;
&lt;p&gt;I can use any project - for example, a PHP or node.js app. Here's an example using PHP.&lt;/p&gt;
&lt;p&gt;This is a manual setup&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Inside my repo, I create:&lt;pre&gt;&lt;code&gt;touch index.php composer.json
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;I add a simple PHP file and install dependencies if needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a setup from GitHub Actions&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Inside the repository, click the &lt;em&gt;Actions&lt;/em&gt; tab&lt;/li&gt;
&lt;li&gt;Search workflow. For example: PHP&lt;/li&gt;
&lt;li&gt;Tap / Click &lt;strong&gt;Configure&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Wait for the workflow finish the deployment.&lt;/li&gt;
&lt;li&gt;Once the workflow deployed. Pull the updates from your local. Run the terminal and type:&lt;pre&gt;&lt;code&gt;    composer init
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;It will add new compose description. Fill the questions.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;create-the-github-action-workflow&quot;&gt;Create the GitHub Action Workflow&lt;/h3&gt;
&lt;p&gt;For Manual set up post. Inside the project, create the directory and file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; mkdir -p .github/workflows
 touch .github/workflows/ci.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;name: PHP CI

on:
  push:
    branches: [ &quot;main&quot; ]
  pull_request:
    branches: [ &quot;main&quot; ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.1'

      - name: Validate composer.json and composer.lock
        run: composer validate

      - name: Run tests (if any)
        run: phpunit

&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;You can modify this workflow for JS, Python, etc.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;commit-and-push-to-github&quot;&gt;Commit and Push to GitHub&lt;/h3&gt;
&lt;p&gt;In VS Code Terminal&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; git add.
 git commit -m &quot;Description here&quot;
 git push origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the changes deployed to the repository, go to your &lt;strong&gt;GitHub Repo -&amp;gt; Actions Tab&lt;/strong&gt; check the workflow running.&lt;/p&gt;
&lt;h3 id=&quot;optional-add-a-simple-test-&quot;&gt;Optional: Add a simple test.&lt;/h3&gt;
&lt;p&gt;If your app has tests. GitHub actions will run automatically. Just make sure your app builds successfully.&lt;/p&gt;
&lt;h4 id=&quot;final-notice&quot;&gt;Final Notice&lt;/h4&gt;
&lt;p&gt;If you successfully work with the CI/CD Actions. The next steps you can try is&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add testing&lt;/li&gt;
&lt;li&gt;Set up deployment (CD)&lt;/li&gt;
&lt;li&gt;Use secrets for environment variables.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>CI/CD Pipelines</category>
<guid isPermaLink="true">https://coderlegion.com/4444/ci-cd-uses</guid>
<pubDate>Tue, 05 Aug 2025 09:37:43 +0000</pubDate>
</item>
</channel>
</rss>