Dodo and the CI/CD Configuration Hell
That Was a Choice is a series about architectural decisions, both good and questionable. Today’s story starts with a YAML file, and ends somewhere near a philosophy of automation.
There’s a special kind of dread reserved for opening a ci.yml
file you didn’t write.
Not because the file itself is inherently scary. On the surface, it's just YAML — a declarative format that's supposed to be human-readable, configuration-centric, and relatively lightweight. But like a lot of things in the modern dev toolchain, CI/CD configuration files tend to rot into opaque, brittle, and overly rigid artifacts. They're not just code. They're infrastructure voodoo, organizational legacy, and implicit process all encoded into a semi-structured mess.
This isn’t a complaint. It’s an observation. And also a confession: I built Dodo because I got sick of pretending that copy-pasting CI workflows from Stack Overflow or actions/starter-workflows
was a “solution.”
What we call “configuration” in CI/CD pipelines today is, quite often, an incoherent mix of imperative logic disguised as declarative intent. You say you’re configuring — but you're really scripting.
The Hell in the YAML
Let’s start from the user’s perspective. You're a developer — solo, maybe, or at a small startup. You want to run tests on push. Maybe lint your code. Deploy on tags. You open .github/workflows/ci.yml
and… it begins.
name: CI
on:
push:
branches:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm install
- run: npm test
So far, fine. But maybe you’re using Rust too. Maybe your repo has a backend in Go, a frontend in Next.js, and a set of Python notebooks that run in a specific Conda environment. Now you’re looking at matrix builds, caching strategies, language-specific setup quirks, version pinning, environment secrets, manual conditional logic for steps that only run on certain paths or events…
Now multiply that across repos.
And orgs.
And contributors who don't know why a pipeline is failing — only that it is.
This Is Not Reproducibility
The goal of CI/CD, in principle, is to make software changes verifiable, testable, and deployable. But if your pipeline becomes an unreadable Rube Goldberg machine stitched together with fragile conditionals and buried context, you’ve created the very thing you were trying to eliminate: unpredictability.
And worse: this mess isn’t easily refactored. Why?
Because CI logic is not treated as code in the same way your application is. There are no standardized abstractions. No good linting. No type system. No enforcement of consistency across projects. No way to validate intent. No clear boundaries between "this is infra policy" vs "this is local dev flow" vs "this is product deployment logic."
Every team reinvents their CI wheel. And most of them reinvent it badly.
What Dodo Is
Dodo is a CI/CD workflow generator and configuration system. But at its heart, it’s an admission: we should not be writing .yml
files by hand.
Dodo is designed to generate, validate, and eventually update CI pipelines — not as a static tool, but as a dynamic interface between your project’s structure and your CI provider. It uses a dodo.toml
file as the canonical spec — think of it like Cargo.toml
or pyproject.toml
but for your pipelines.
[]
= ["python", "rust"]
= true
= true
= { = "vercel", = "tags" }
[]
= "-Dwarnings"
= "3.11"
From this, Dodo generates a full-fledged GitHub Actions YAML that covers installs, caching, matrix builds, secrets, deployment hooks, and CI best practices — automatically. And it adapts to the structure of your repo: whether it's monorepo, polyglot, or multi-target.
And if your team prefers warnings over enforcement? Dodo can issue soft validations instead of hard errors.
Intent Over Syntax
Dodo exists to encode intent, not syntax.
You don’t care if actions/setup-python@v5
needs cache: 'pip'
or if your job should uses: actions/cache@v4
with path: ~/.cargo/registry
— you care that your build is reproducible, fast, and secure.
You care that tests run. That linting fails loudly. That secrets aren’t accidentally exposed. That a new contributor doesn’t have to spend 2 days reverse-engineering how to fix a CI failure that has nothing to do with their PR.
And yet, we’ve forced every engineer to become a YAML janitor. Dodo’s job is to remove that.
Why TOML?
Why not just build a CLI tool that edits .yml
directly?
Because declarative clarity matters. CI logic needs to be portable, introspectable, and auto-generated. YAML isn’t the right place to express that. It’s the output format, not the source of truth.
TOML gives us:
- A clean structure for describing intentions
- A readable interface for human authors
- Compatibility with tooling ecosystems (Rust, Python, etc.)
- Easy embedding for future plugins
Think of it this way: Dodo uses dodo.toml
the way Terraform uses .tf
— not as implementation, but as orchestration.
Pluggable Intelligence
Under the hood, Dodo is AI-aware — but not AI-dependent. The generation engine is pluggable, with models like Phi-3 (on-device) used for complex inference. But you can swap the engine. Run offline. Disable AI completely.
AI is there for structure inference, when needed — not to hallucinate pipeline logic. It can tell you what you probably meant, but it never pretends to be the source of truth. That’s your config.
In future releases, Dodo will support live template inference (via raphus.io
), auto-patching for deprecated actions, and consistency enforcement across repos. Think of it as Renovate meets CI meets a language server.
Version Drift and Decay
Here’s a dirty secret: most CI pipelines are out-of-date.
Not in the catastrophic “you’re about to break prod” sense, but in the quiet, compounding way that technical debt accrues. Your workflow uses an old version of an Action. Your cache paths are suboptimal. Your deployment step has been deprecated. Your tests are slow because parallelization isn’t tuned.
Dodo’s upcoming updater is designed to detect and fix this — safely. Not just bump-version
like Dependabot, but semantically-aware updates that know the context in which each action runs.
CI rot is real. Dodo prunes it.
Opinionated, But Optional
Dodo is not trying to be everything to everyone.
It assumes sane defaults. It’s opinionated about caching, parallelism, test structure, and secrets. But it never forces you to follow a rigid structure. Every setting can be toggled. You can inject custom steps. Override templates. Mix manual and generated workflows.
It’s not about taking away control. It’s about taking away tedium.
You should still be able to ship your weird Python-Haskell hybrid pipeline with ML model diffs and PDF report generation. But you shouldn’t have to debug if: github.event_name == 'pull_request' && github.actor != 'dependabot'
just to get there.
The Broader Philosophy
What Dodo really asks is: Should CI logic be handwritten code?
Or should it be inferred, adapted, validated, and versioned — like everything else in modern infra?
Dodo isn’t just a dev tool. It’s a stance.
That CI/CD is infrastructure, and infrastructure should be reproducible, explainable, and shareable. That humans shouldn’t need to memorize uses: actions/setup-node@v3
to run a unit test. That the pipeline should evolve with your codebase — not be duct-taped beside it.
That was a choice. Let’s make better ones.
Dodo is still growing. But the ambition is steady: less friction, fewer errors, and pipelines that feel like part of your codebase — not an obstacle course you dread stepping into.
If you're tired of babysitting YAML, Dodo's not magic.
But it might be flight.