CodeDeploy is a deployment controller: it takes a versioned bundle of your application, distributes it to a set of targets, and runs your scripts at defined points in a lifecycle, rolling back automatically if a health check fails. The configuration all lives in one YAML file called appspec.yml.
Most explanations of CodeDeploy are either AWS reference documentation or a tutorial that gets one example working without saying what the moving parts are for. It is worth understanding the model on its own terms, because the same concepts — a deployment as a discrete versioned event, lifecycle hooks, a health gate, and an automatic rollback — turn up in every deployment system, and knowing them makes the alternatives easier to judge.
Table of contents
- The problem a deployment controller solves
- The appspec file
- Lifecycle hooks in order
- In-place versus blue-green
- What it costs to operate
- When you do not need a deployment controller
- How this fits the rest of the stack
- FAQ
The problem a deployment controller solves
Copying files onto a server is not a deployment. It is one step of a deployment, and the other steps are what go wrong.
A real deployment has to: stop taking traffic on the instance being updated, stop the old process, put new files in place, install dependencies, run migrations, start the new process, check it is healthy, and put it back into rotation. Across several instances, without taking them all down at once. And if the new version is broken, undo all of that.
Doing that with a shell script works until the day it half-succeeds. Two instances updated, one failed midway, and the fleet is now running two versions with no record of which is where.
A deployment controller makes the deployment a first-class object: it has an id, a revision, a status, a per-instance log, and a defined behaviour on failure. That is the actual value, and it is why the concept exists across every platform rather than being an AWS invention.
The appspec file
appspec.yml sits at the root of your bundle and tells CodeDeploy two things: which files go where, and which scripts run when.
version: 0.0
os: linux
files:
- source: /
destination: /var/www/myapp
permissions:
- object: /var/www/myapp
owner: www-data
group: www-data
mode: 755
hooks:
ApplicationStop:
- location: scripts/stop_server.sh
timeout: 60
runas: root
BeforeInstall:
- location: scripts/before_install.sh
timeout: 300
runas: root
AfterInstall:
- location: scripts/after_install.sh
timeout: 300
runas: root
ApplicationStart:
- location: scripts/start_server.sh
timeout: 60
runas: root
ValidateService:
- location: scripts/health_check.sh
timeout: 120
runas: root
For EC2 and on-premises targets the file must be named appspec.yml exactly and sit at the root of the bundle. For Lambda and ECS deployments the structure differs and the file can be YAML or JSON.
Two things bite people repeatedly. The file is YAML, so indentation is significant and tabs are invalid — a deployment failing at the very first step with a parse error is nearly always this. And ApplicationStop runs from the previously deployed revision, not the new one, which means a broken stop script sticks around and blocks the next deployment until you work out why.
Lifecycle hooks in order
The hooks fire in a fixed sequence, and knowing what runs when is most of understanding the system.
- ApplicationStop — stop the running application. Runs from the previous revision.
- DownloadBundle — CodeDeploy copies the new revision down. Not scriptable.
- BeforeInstall — pre-installation work such as backups or decrypting configuration.
- Install — files are copied to their destinations. Not scriptable.
- AfterInstall — the main hook. Dependency installation, asset builds, configuration, permissions.
- ApplicationStart — start the new version.
- ValidateService — verify it works. Anything non-zero here fails the deployment.
ValidateService is the one that earns the whole setup. A script that curls the local health endpoint and exits non-zero if it does not return 200 turns a bad deployment into an automatic rollback instead of an outage somebody notices later.
#!/bin/bash
for i in {1..30}; do
if curl -fsS http://localhost:8080/health > /dev/null; then
echo "healthy after ${i}s"
exit 0
fi
sleep 1
done
echo "health check failed"
exit 1
Give every hook a realistic timeout. The default is an hour, which means a hung script blocks the deployment for an hour before anyone finds out.
Blue-green deployments add further hooks around traffic switching — BeforeAllowTraffic and AfterAllowTraffic — which is where you would warm a cache or run a smoke test before the load balancer starts sending real requests.
In-place versus blue-green
Two deployment styles with genuinely different trade-offs.
In-place updates the existing instances. Cheaper, since no new capacity is provisioned, and slower to roll back because rolling back means deploying the previous revision through the same process. Combined with a rolling configuration — one instance at a time, or half at a time — it keeps the service available throughout.
Blue-green provisions a parallel set of instances, deploys to those, health-checks them, and then switches the load balancer over. Rollback is switching the load balancer back, which is close to instant. The cost is running two fleets during the deployment.
The choice comes down to how much a bad deployment costs you. If a rollback taking ten minutes is acceptable, in-place is simpler and cheaper. If it is not, blue-green buys you a near-instant reversal at the price of temporary double capacity.
One thing neither style solves is database migrations. A schema change is not rolled back by switching a load balancer, and a blue-green deployment where both fleets briefly serve traffic requires the schema to be compatible with both versions of the code. That constraint — expand the schema, deploy the code, contract the schema later — is the actual hard part of zero-downtime deployment, and no deployment controller does it for you.
What it costs to operate
Worth being honest about, because the tutorials show the happy path.
CodeDeploy needs an agent installed and running on every EC2 target, kept updated, and it needs IAM roles configured for both the service and the instances. It expects your bundle in S3 or GitHub. It usually sits inside a larger pipeline that fetches source, builds, and then triggers the deployment, which is several more services to configure.
The failure modes reflect that. An agent that has stopped means an instance is skipped silently. An IAM policy missing one permission produces a failure at a stage whose error message does not name the permission. A deployment stuck at ApplicationStop because a script from the previous revision is broken requires understanding a lifecycle detail to diagnose.
None of this is unreasonable for a fleet of instances with strict availability requirements. It is a lot of machinery for a single application server, and the amount of configuration is roughly the same either way, which is the honest thing to weigh.
When you do not need a deployment controller
The concepts here — versioned revisions, health-gated releases, automatic rollback — are worth having on any project. Assembling them from separate services is not.
If your application is a single service or a handful of them, a platform that builds from a commit and gives you a live route already provides the same properties with none of the assembly: the deploy is a discrete event with a log, the previous release stays available, and rolling back is selecting it.
That is how deploys work on RunxBuild — push to the repository, get a build log and a live route, and roll back to a previous deploy when one goes wrong. There is no agent to install and no lifecycle YAML, because the platform owns the lifecycle.
The trade is control. A deployment controller lets you script arbitrary behaviour at seven defined points, which matters when your deployment genuinely needs to drain a queue, warm a cache, and coordinate with something else. A platform makes reasonable choices for you, which matters when your deployment is build, start, health-check, switch.
The question worth asking is which of those two descriptions matches your deployment. Most teams answer the second and configure for the first, then maintain the difference indefinitely.
How this fits the rest of the stack
CodeDeploy’s real content is a model rather than a product: deployments as versioned events, scripts at defined lifecycle points, a health gate, and an automatic rollback. Those properties are worth having whatever you deploy with — and the interesting question is whether you assemble them or get them by default. The RunxBuild hosting calculator shows the service, the database, and the storage as separate line items, which is a fair way to compare against a stack where the deployment tooling is several services of its own.
Useful related references:
- GCP vs AWS: Pricing, Network, and When to Pick Each
- Decentralized Computing vs Cloud Computing: AWS/GCP vs DePIN
- Competitors for AWS: GCP, Azure, and the Smaller Clouds
- Services on RunxBuild
FAQ
What is the appspec.yml file in CodeDeploy?
The configuration file at the root of your deployment bundle. It declares which files are copied where, what permissions they get, and which of your scripts run at each lifecycle hook. For EC2 and on-premises deployments it must be named exactly appspec.yml and sit at the bundle root; ECS and Lambda deployments use a different structure.
What is the difference between in-place and blue-green deployment?
In-place updates your existing instances, which is cheaper but slower to roll back since reverting means running another deployment. Blue-green provisions a parallel fleet, health-checks it, and switches the load balancer, so rollback is near-instant at the cost of temporarily running double capacity.
Why does my CodeDeploy deployment fail at ApplicationStop?
Because that hook runs the script from the previously deployed revision, not the new one. A broken stop script persists and blocks subsequent deployments until it is fixed. It also fails if the agent cannot find the previous revision at all, which happens after manual changes on the instance.
Do I need CodeDeploy for a small application?
Probably not. It requires an agent on every target, IAM configuration, a bundle in S3 or GitHub, and usually a surrounding pipeline. For one or two services, a platform that builds from a commit and keeps the previous release available gives you versioned deploys, logs, and rollback without any of that assembly.
Does CodeDeploy handle database migrations?
No, and no deployment controller does. Switching a load balancer back does not undo a schema change, and during a blue-green deployment both versions of your code may be live at once against one database. That requires an expand-then-contract migration approach, which is a design constraint on your schema rather than a tooling feature.