Skip to content

Beyond Just Deploy

Question

How do I ensure my agent writes infrastructure-as-code that accounts for more than just the initial deployment of the application?

Solution

Provide the agent with some context about concepts like infrastructure lifecycle operations by providing it with a working example of a demo application with a modular codebase.

Explanation

Every example in this talk is cut from one real, running repository: this one. apps/appctl is the dispatcher every example comes back to - one generic orchestrator, not five separate scripts:

usage: ./appctl <verb> <app> --env <name>

  build    <app> --env NAME   gather every dependency needed to install
  update   <app> --env NAME   apply a day-2 change to a running instance
  test     <app> --env NAME   verify the app; what/when differs per app
  deploy   <app> --env NAME   first-time install (maybe just calls update)
  destroy  <app> --env NAME   tear down - full or selective - and it is gone

Source: apps/appctl

appctl itself knows nothing about any one application - it only parses a verb, an app name, and an environment, then hands off to that app's own code. "Context about infrastructure lifecycle operations" is not a paragraph you paste into a prompt; it is this file existing at all, and an agent being pointed at it before being asked to write anything.

The app-specific half of that handoff lives one level down, in apps/demo-app/libs/ - one file per lifecycle operation, not one script that branches on an argument:

apps/demo-app/libs/
├── appctl-build
├── appctl-deploy
├── appctl-destroy
├── appctl-test
└── appctl-update

Source: apps/demo-app/libs/

None of these five names is "deploy a web app" or "deploy a database" - they are named for the lifecycle operation they perform, on whichever app appctl was told to run them against, deliberately generic about how each one is implemented:

  • appctl-build gathers everything the app needs to install - not necessarily a container image: an RPM, a .deb, a whole VM image are all "build" too. Imagine shipping into an air-gapped environment with no reachable registry or package mirror - gathering the full stack's dependencies ahead of time is exactly what build is for. For demo-app, that happens to be one docker pull of a pinned tag.
  • appctl-deploy is the first-time install. For an app whose install logic is fully idempotent, deploy can just call update - demo-app's does - but an app with install-only steps (a migration, a seed job) needs its own deploy, kept separate, so those steps never repeat on every update.
  • appctl-update is the day-2 operation: changes to an already-running instance. Risk varies enormously by app - a config tweak or a logging-agent bump is low-risk, a database schema migration needing planned downtime is not. For demo-app, update only ever means a new nginx tag, and maybe new page content.
  • appctl-test verifies the app, but what that means and when it runs differs by app: continuous monitoring of an app's key interfaces, or a one-off check right after an install or an update.
  • appctl-destroy tears down, full or selective - a web frontend only, or web and database; a container only, or container and volumes. Most useful for iterative infrastructure-as-code development, and for tearing down the old side of an A/B cutover once traffic has moved.

An agent that has walked this structure once has the vocabulary for "update" and "destroy" before it is ever asked to use either one.