Guide

Export, Review, Apply: The Codenize Workflow

Export, Review, Apply: The Codenize Workflow
In short

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.

Key takeaways
  • 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:

StepCommand (Roadworker)What it does
Exportroadwork -e -o RoutefileDump live Route 53 state into a DSL file
Commitgit add Routefile && git commitEstablish the baseline in version control
Edit$EDITOR RoutefileDescribe the new desired state
Dry-runroadwork -a --dry-runPreview the exact delta, write nothing
Applyroadwork -aMake 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. A make dry-run target 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 rrset deletes 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/0 ingress, 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* and Get* 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, not AdministratorAccess. 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.

Frequently asked questions

Do I have to export before I can apply?
In practice, yes. The export is your baseline: it captures everything the tool considers in scope, and a clean dry-run against it proves you start converged. Applying a hand-written file against an account with existing resources risks deleting anything the file fails to mention.
Can I manage multiple AWS accounts from one repository?
Yes. Give each account or environment its own directory with its own DSL files, and select credentials per directory with AWS_PROFILE or per-job CI credentials. Avoid parameterizing one file across environments; a staging change should never touch lines that describe production.
What if a dry-run shows changes I didn't make?
That is drift: someone or something modified the live service outside the repo. Decide case by case whether to adopt the change by re-exporting and committing it, or to revert it by applying the file as-is. Either way, resolve it before layering your own change on top.
How do I roll back a bad apply?
Revert the commit in Git, dry-run to confirm the delta is the inverse of the bad change, and apply. Idempotency makes this identical to any other change. The caveats are data and caches: dropped tables come back empty, and DNS clients hold old records until TTLs expire.