Export, Review, Apply: The Codenize Workflow
The codenize workflow is a five-step loop: export live config to a DSL file, commit the baseline to Git, edit it, dry-run to preview the exact change, then apply. Because applies are idempotent, re-running is always safe and rollback is just reverting the file. The same steps and flags work across every tool in the family.
- Always commit the unmodified export first; a clean dry-run then proves you start converged.
- Dry-run before every apply to preview the exact delta, and after it to confirm convergence.
- Use one directory per account or environment so a staging change never touches production lines.
- In review, read deletions first: an absent block means a destroyed resource.
- Give PR jobs read-only credentials and apply jobs write access scoped to one service.
- Learn the loop once with tools like Barkdog; the same verbs work everywhere.
Every codenize tool works the same way. Learn the workflow once, and it pays off across the whole family. This guide walks through it end to end, using Roadworker and Route 53 as the example.
Then it covers the parts that matter on a real team: repository layout, code review, CI, credentials, and rollback. If you want the concept before the commands, start with What Is Codenization? and come back.
The Workflow at a Glance
The whole loop is five steps. Here they are with Roadworker commands:
| Step | Command (Roadworker) | What it does |
|---|---|---|
| Export | roadwork -e -o Routefile | Dump live Route 53 state into a DSL file |
| Commit | git add Routefile && git commit | Establish the baseline in version control |
| Edit | $EDITOR Routefile | Describe the new desired state |
| Dry-run | roadwork -a --dry-run | Preview the exact delta, write nothing |
| Apply | roadwork -a | Make the API match the file |
The verbs are nearly identical across the family. Use miam -e for IAM, gratan -e for MySQL grants, ridgepole --export for schemas, and barkdog -e for Datadog monitors. Learn the table once, then reuse it everywhere.
One caveat up front. Gem maintenance across the family is uneven. Ridgepole is active, but several AWS gems are dormant. Check a tool's status on its index page here before you wire it into anything important.
Step 1: Export and Commit the Baseline
Install the gem and point it at your account. Roadworker uses the standard AWS SDK credential chain, so environment variables and named profiles both work:
gem install roadworker
export AWS_REGION=us-east-1 # credentials via env vars or AWS_PROFILE
roadwork -e -o Routefile
The export writes every hosted zone and record set in the account as a Ruby DSL:
hosted_zone "example.com." do
rrset "example.com.", "A" do
ttl 300
resource_records "192.0.2.10"
end
end
Commit before you touch anything
Commit exactly what was exported, before any edits:
git init
git add Routefile
git commit -m "Route 53: baseline export, unmodified"
The untouched baseline matters. A dry-run against it should report no changes. If it reports something, you found API normalization noise, or drift happening under you. Resolve that first, before you mix it into a real edit.
Steps 2-4: Edit, Dry-Run, Apply
Make one logical change. Say the team needs an api record:
rrset "api.example.com.", "A" do
ttl 60
resource_records "192.0.2.20"
end
Preview it before writing anything:
$ roadwork -a --dry-run
Create ResourceRecordSet: api.example.com. A (dry-run)
The dry-run output is the computed diff between file and live API. It is the change itself, not a guess. If it matches your intent, apply, then confirm convergence:
$ roadwork -a
Create ResourceRecordSet: api.example.com. A
$ roadwork -a --dry-run # should now report no changes
Keep every commit small
Roadworker has one extra verb worth knowing. roadwork -t runs the Routefile as a test suite against live DNS. It checks that the world really answers the way the file says.
Keep commits small. One logical change per apply keeps every diff, review, and revert easy to read. Why re-applying is always safe is the subject of Idempotency in Infrastructure as Code, Explained.
Structuring the Config Repo
Use one repository for service config. Use one directory per account or environment. Keep default filenames so commands work from the folder they live in:
infra-config/
├── aws/
│ ├── production/
│ │ ├── Routefile # Route 53 (Roadworker)
│ │ ├── IAMfile # IAM (Miam)
│ │ └── Groupfile # security groups (Piculet)
│ └── staging/
│ └── Routefile
├── db/
│ └── orders/
│ ├── Schemafile # schema (Ridgepole)
│ └── Grantfile # grants (Gratan)
└── datadog/
└── Barkfile # monitors (Barkdog)
Three rules that keep it sane
- One directory per account or environment. Never parameterize one file across environments. The diff for "change staging" must not touch production lines.
- Wrap each tool in a Makefile. Nobody should have to remember flags. Ridgepole needs
-c config.yml -E production, and Barkdog needs a Datadog API key. Amake dry-runtarget hides all of it. - Add a short README per directory. State which credentials the tool expects and which account it targets.
A monorepo also gives you one place to watch. Every infrastructure change in the company lands in a single log of reviewed commits.
Reviewing Infrastructure Changes
A pull request against a DSL file is real code review. Reviewers should read it with a specific checklist:
- Deletions first. In a declarative file, an absent block is a destroyed resource. A removed
rrsetdeletes a DNS record. A removed column in a Schemafile drops data. Treat every red line as the most dangerous line in the diff. - Blast radius of edits. TTL drops, failover changes, and grant-host widening (
"10.0.%.%"becoming"%") look small in a diff but are not. - Over-broad access.
0.0.0.0/0ingress, IAM statements with"Action": "*", or GRANT ALL should be rare and justified in the PR. - The dry-run output, pasted into the PR. Reviewers should see the computed delta, not just the source diff. The two differ exactly when something unexpected is going on.
The broader case for putting this class of change through pull requests is made in Infrastructure Config in Version Control.
CI Integration
Two jobs cover it. Dry-run on every pull request. Apply on merge to the default branch.
# .github/workflows/route53.yml (excerpt)
on:
pull_request:
paths: ["aws/production/Routefile"]
jobs:
dry-run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: gem install roadworker
- run: cd aws/production && roadwork -a --dry-run
Add a nightly drift check
Post the dry-run output as a PR comment, so the delta becomes part of the review record. The merge-triggered apply job is the same, minus --dry-run. Gate it on the default branch and, ideally, on an environment with required approval.
Add a third job that dry-runs nightly against main. Non-empty output means someone changed production outside the repo. That pattern, and what to do when it fires, is covered in Dry-Run and Drift Detection.
One operational rule: serialize applies. Two applies racing each other will not corrupt anything, since each converges toward its own file. But the interleaved output is unreadable, and the loser's dry-run no longer matches what happened. Use your CI system's concurrency controls to queue apply jobs per directory.
Credentials and Least Privilege
The AWS tools all use the Ruby SDK's standard chain: environment variables, AWS_PROFILE, instance or task roles, and short-lived OIDC credentials in CI. Prefer that last option over long-lived keys in CI secrets.
Database tools take explicit connection settings. Ridgepole reads a config.yml. Gratan needs a MySQL account that itself holds GRANT OPTION, which makes that account one of the most sensitive credentials in the pipeline.
- PR jobs get read-only credentials. Route 53
List*andGet*actions are enough for a dry-run. A dry-run that cannot write is a guarantee, not a convention. - Apply jobs get write access scoped to one service. Use
route53:ChangeResourceRecordSets, notAdministratorAccess. Note the recursion with Miam: a tool that manages IAM needs IAM write access, so its apply job is the crown jewels. Protect it with required reviewers and environment gates. - No secrets in the repo. The DSL files describe configuration, never credentials. Anything secret rides in CI secret storage or a secrets manager.
Rollback Strategy
Applies are idempotent, and the file is the whole desired state. So rollback is not a special procedure. It is the same procedure with an older file:
git revert HEAD # undo the bad change in the file
roadwork -a --dry-run # confirm the delta is the inverse
roadwork -a # converge back
Three honest caveats come with it:
- Reverting config does not resurrect data. A dropped table comes back empty. Schema rollbacks need backups, not just Git.
- DNS rollbacks propagate at the old record's TTL. Clients may see the bad value until caches expire. Lower TTLs before risky changes.
- Watch for emergency fixes. If the bad apply triggered out-of-band fixes, dry-run before you re-apply, or the rollback erases those too.
None of this diminishes the core win: the change history and the undo button live in the same place as the rest of your code.