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-buildgathers 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. Fordemo-app, that happens to be onedocker pullof a pinned tag.appctl-deployis 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-updateis 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. Fordemo-app, update only ever means a new nginx tag, and maybe new page content.appctl-testverifies 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-destroytears 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.