Back to list
    Post-Deploy Cleanup: Updating Docs, Revising the IaC Design, and Handing Off to the Team
    Dev Labo
    PRThis article contains advertisements

    Post-Deploy Cleanup: Updating Docs, Revising the IaC Design, and Handing Off to the Team

    24 min read

    In the previous article I wrote about the day-of work for the Canary SG: creating the SG, adding ingress rules to the ECS SGs, and running the Pulumi deploy.

    This is what came the next day and after. A record of the cleanup that happens "after the production deploy is done".

    It's easy to write up the deploy day and feel finished, but in reality there's quite a bit left to do afterwards: updating doc statuses, recording policy changes, designing the handoff communication. It's unglamorous work, but if you skip it, "wait, what happened with that?" shows up a few weeks later.

    Overall scope

    What I did this time:

    1. Updated each endpoint's deploy status to "production complete"
    2. Reflected the IaC management policy change in the docs (using strikethrough to preserve the history)
    3. Designed the handoff communication to the development team

    Going through them in order.

    1. Updating the endpoint tracking doc

    Starting with the mechanical work. There's a doc that tracks the progress of monitoring endpoints, and each endpoint has a status like "configured (dev)" / "verified" / "configured (prod)".

    With the production deploy complete, the task was to flip all of them to configured (prod).

    <!-- before -->
    - **Status:** configured (dev)
    
    <!-- after -->
    - **Status:** configured (prod)

    It's a simple bulk update, but I had Claude Code load it and walk through it phase by phase. "Verified" and "configured (dev)" were mixed together, and both should ultimately become configured (prod). Being able to confirm one entry at a time while looking at the diff was nice.

    2. Recording the IaC design change in the docs

    This is the piece I'm most glad I did this time around.

    Background

    I touched on this in the previous article, but originally the design called for managing the SG (security group) in Pulumi too. When I actually ran it, though, the SG rules Pulumi had set were getting stripped off whenever the Terraform deploy ran, and monitoring connectivity broke.

    The cause was simple: Terraform and Pulumi both saw the same resource as "mine," and they conflicted. When terraform apply runs, Terraform's definition is treated as the source of truth, and the rules Pulumi added disappear.

    The fix was to keep the SG under the existing Terraform management, while continuing to manage the monitoring resources (Canary / Alarm / SNS) in Pulumi. By drawing a clear ownership boundary, the conflict goes away.

    [Pulumi-managed]
      ├─ IAM Role / Policy   : Canary execution role
      ├─ CloudWatch Synthetics Canary x N
      ├─ CloudWatch Alarm x N
      ├─ SNS Topic
      └─ SNS Subscription
    
    [Terraform-managed (existing setup)]
      └─ Security Group / Rules : Allow Canary to reach the targets

    How to update the doc: keep the history with strikethrough

    I hesitated a bit about how to record the change. The simple approach is "delete the old description and replace it with the new one." But this time I chose to keep the old description as strikethrough and add the new policy below it.

    <!-- before -->
    AWS resources are built with Pulumi (IaC), in line with the company-wide adoption.
    Manual configuration becomes technical debt, so monitoring resources like Canary /
    Alarms / SNS are all managed in code.
    
    <!-- after -->
    ~~AWS resources are built with Pulumi (IaC), in line with the company-wide adoption.
    Manual configuration becomes technical debt, so monitoring resources like Canary /
    Alarms / SNS are all managed in code.~~
    
    AWS resources are built with both Pulumi (IaC) and Terraform — monitoring resources
    like Canary / Alarms / SNS are managed in Pulumi, but security groups (SGs) are
    delegated to the existing Terraform setup. Reason: when SGs were managed in Pulumi,
    the rules Pulumi set got stripped off whenever the Terraform deploy ran, and
    monitoring connectivity broke. Responsibility is now clearly split: SGs in
    Terraform, everything else (monitoring) in Pulumi.

    Why not just delete the old text? Because I wanted to preserve "why this happened." If only the new policy is written, anyone reading later (including future me) will wonder "why are we using both Pulumi and Terraform?" and the answer won't be there. With strikethrough, you can see at a glance: "this was the original plan, but here's the problem we hit, and that's why it changed."

    Terraform vs. Pulumi conflict: what was actually happening

    A bit of additional context. When you mix multiple IaC tools, you can run into "who owns this resource" conflicts.

    In this case, when Pulumi added the SG rule, that rule did not exist in Terraform's state. So when terraform apply ran, Terraform's behavior was: "there's a rule that isn't in my definition → delete it."

    There are basically two ways to deal with this.

    1. Pick one tool and stick with it for that resource.

    The approach I took. SGs are fully owned by Terraform, and Pulumi never touches them. Simple, no conflicts.

    2. Use Terraform's lifecycle { ignore_changes }.

    You tell Terraform "ignore changes to specific fields on this resource." This gets configuration-heavy quickly, and there's a risk of missing unintended resource changes.

    Since there was already a working Terraform-managed SG setup, going along with it was the lower-risk decision.

    3. Designing the handoff to the development team

    With the production deploy done, there were still items I needed the development team to confirm.

    • Final decision on monitoring intervals: Currently all endpoints are running on a temporary 30-minute interval. What granularity is sufficient operationally?
    • Alert threshold policy: Currently set as a temporary "notify after two consecutive errors / also notify on recovery." What's the acceptable level of false positives, and what should the initial policy be?

    The first message Claude Code drafted for me looked something like this.

    (Person on the dev team),

    Hi — the production deploy of the synthetic monitoring is complete.

    Following up on the action items from the March meeting around "monitoring interval review" and the alert threshold policy needed to enter the operations phase… (snip)

    1. Monitoring interval per endpoint (dev / prod)
    - Currently all endpoints are temporarily running on rate(30 minutes)
    - Shorter intervals catch incidents faster but cost more
    - Please share what granularity is sufficient operationally for dev / prod
    2. Alert thresholds and firing conditions
    - Currently set per the temporary policy above
    …(continues)

    Long. I realized I wouldn't want to read this myself, so I switched gears to "let's just talk in a meeting." The actual message I sent ended up like this.

    (Person),

    Hi — the synthetic monitoring deploy through to production is complete. Thanks for your help.

    As we move into the operations phase, I'd like to align on the following two items, so I've put a meeting on the shared calendar. Please take a look.

    - Final decision on monitoring intervals (the March action item)
    - Alert thresholds and firing conditions (finalize the temporary policy + how we'll tune going forward)

    Should take about 15–30 minutes.

    If the time doesn't work, just let me know and I'll reschedule. Thanks.

    The key choice was to put the meeting on the shared calendar first, then send the message. That cuts out the round trip of asking for candidate times, getting a reply, then negotiating again.

    Also, by framing it as "let's discuss the details in the meeting," I no longer had to explain everything inside the message itself. The option matrix (cost comparisons, etc.) is much easier to absorb as meeting material, both for the writer and the reader.

    Retrospective

    I wrote up the deploy day in the previous post, but honestly the cleanup felt like it took more time.

    Update doc statuses first. Lock in the fact of "this is done" by updating the status before anything else. If you put it off, you start getting fuzzy on "wait, did we deploy that endpoint to prod or not?"

    Don't delete design changes — preserve the history. Use strikethrough to give the doc a story: "this was the original plan, but here's the reason it changed." You can also chase it through git history, but reading it inline as context is overwhelmingly easier.

    Design communication for "minimum round trips." There are many cases where actually talking in a meeting beats handing over a list of options in writing. Reserving the calendar slot first and then messaging is a small move, but it makes a real difference.

    Even after the production deploy is done, you stay in "just-launched" mode for a while: finalizing intervals and thresholds, handing off to the team, maintaining the docs. I hope this record helps with that phase for someone.

    Further reading

    If you want to learn how to write and maintain technical documentation systematically, the following is a useful reference.

    If you want to dig deeper into recording the history of design changes (ADRs and the like), this book is also worth a look.

    For learning how to design communication between engineers and across teams, the following book is recommended.

    Was this article helpful?

    Coffee cup

    If this article helped you organize your thoughts

    a coffee-sized support would be much appreciated.

    ※ This is separate from tipping, but—

    If you'd like to organize similar themes in your own context, I also offer dialogue sessions as a form of thought organization.

    About Dialogue Sessions