39.7 CodeDeploy Deployment Groups, AppSpec, and Lifecycle Hooks
Right, so you’ve got your code built and packaged. Now comes the fun part: actually getting it onto your fleet of instances without causing a complete, user-noticing meltdown. This is where CodeDeploy earns its keep, and where most people get tripped up by its particular… let’s call them idiosyncrasies. Think of CodeDeploy not as a simple file copier, but as a meticulous stage manager for your deployment play. It needs a script (the AppSpec file) and a cast list (the Deployment Group). Let’s break it down.
The Deployment Group: Your Cast of Characters
A Deployment Group is simply CodeDeploy’s way of knowing which instances to deploy to. You don’t specify individual instances; you point it to an Auto Scaling Group or tag your EC2 instances. The tag-based approach is far more flexible.
# This isn't a config file you run, it's how you'd think about tagging an EC2 instance.
# CodeDeploy will look for instances with these tags.
Key: Environment
Value: production
Key: Application
Value: my-awesome-api
You’ll then tell your Deployment Group to target instances with the tag Environment=production and Application=my-awesome-api. The brilliant part? Any new instance that spins up in your Auto Scaling Group with these tags is automatically added to the deployment pool. The terrifying part? The same. Make sure your tags are correct.
The pitfall here is assuming CodeDeploy can just find your instances. It can’t. They must have the CodeDeploy Agent installed and running. This is a non-negotiable prerequisite. If the agent isn’t running, your deployment will fail, and the error message is about as helpful as a screen door on a submarine. Always check your agent logs on a new instance (/var/log/aws/codedeploy-agent/) if things go sideways.
The AppSpec File: The Script
The appspec.yml file is the heart of the operation. It’s a YAML file placed in the root of your application’s source bundle. It tells CodeDeploy two things: 1) where to put the files, and 2) what scripts to run during the deployment lifecycle. It’s deceptively simple, which is why people mess it up.
# This is your appspec.yml file. It lives in your root directory.
version: 0.0
os: linux
files:
- source: /
destination: /var/www/my-app
hooks:
BeforeInstall:
- location: scripts/install_dependencies.sh
timeout: 300
AfterInstall:
- location: scripts/change_file_owner.sh
ApplicationStart:
- location: scripts/start_server.sh
ApplicationStop:
- location: scripts/stop_server.sh
The files section is straightforward: “copy everything from the source bundle’s root (source: /) to /var/www/my-app on the instance.” Now, the hooks. This is where the magic (or disaster) happens.
Lifecycle Hooks: The Stage Directions
CodeDeploy doesn’t just blast files onto a live server. It walks through a defined series of lifecycle hooks. You can hook into these points to run scripts. The order of execution for an in-place deployment is crucial:
- ApplicationStop: Stops your current application. Great for gracefully shutting down a service.
- DownloadBundle: CodeDeploy grabs the new revision. You don’t script this.
- BeforeInstall: Run tasks before the new files are installed. Think backup operations or pre-install config.
- Install: CodeDeploy copies the new files over the old ones. You don’t script this.
- AfterInstall: Run tasks after the new files are in place. Your classic
npm installorcomposer installlives here. - ApplicationStart: Starts your application back up. This is where you fire up your web server.
- ValidateService: Run tests to ensure the deployment was successful. This is your last line of defense before CodeDeploy calls it “successful.”
The most common pitfall? Forgetting to make your scripts executable. I’m not joking. You’ll spend an hour debugging a cryptic failure only to find chmod +x scripts/start_server.sh was the solution. Also, your scripts must be idempotent. A deployment can fail and retry, so scripts shouldn’t assume they’re running on a pristine system.
Here’s a realistic, brutally simple example of a start script:
#!/bin/bash
# scripts/start_server.sh
# This script must be executable: chmod +x scripts/start_server.sh
# Navigate to our app directory
cd /var/www/my-app
# Start the Node.js app as a background process, telling it to use the production environment
# The '>' redirects output to a log file.
nohup node app.js > /var/log/my-app.log 2>&1 &
Why nohup and &? Because the script needs to exit for CodeDeploy to continue. If you run a process in the foreground, the script will hang forever, and CodeDeploy will eventually time out and fail the deployment. It’s a harsh teacher.
The Golden Rule: Idempotency and Failure
CodeDeploy is built for resilience. If a single script in a single hook fails on a single instance, it will stop the deployment and, depending on your configuration, possibly roll back. This is a good thing! It prevents half-baked deployments from going live.
Your job is to write scripts that can fail gracefully or be run multiple times without breaking things. This is the real “trenches” knowledge. Don’t rm -rf anything important in a script. Always check if a service is already running before trying to start it. Assume any command can fail and handle it. CodeDeploy is powerful precisely because it forces you to think about these edge cases, making your deployment process more robust than a simple, mindless scp.