Alright, let’s talk about getting your code out of the build phase and into the real world without causing a five-alarm fire. This is where CodeDeploy takes the baton. Its entire reason for being is to answer the terrifying question: “How do I actually deploy this thing?” It handles two main deployment types, and your choice here is the single biggest factor in whether you sleep well at night.

First, the classic: in-place deployments. This is the “hold my beer” approach. CodeDeploy connects to your existing fleet of EC2 instances (or Auto Scaling group) and systematically replaces the application code on each one, server by server. It does this using a deployment configuration that dictates how many servers can be taken down at once. You might say “all at once” (which is just asking for trouble), or, more sensibly, do a rolling update.

The magic is in the appspec.yml file. This is CodeDeploy’s instruction manual for a single deployment. It tells the CodeDeploy agent on your instance what to do, step-by-step. Here’s a realistic one for a simple web app:

version: 0.0
os: linux
hooks:
  BeforeInstall:
    - location: scripts/stop_server.sh
      timeout: 300
  AfterInstall:
    - location: scripts/install_dependencies.sh
      timeout: 300
  ApplicationStart:
    - location: scripts/start_server.sh
      timeout: 300
  ValidateService:
    - location: scripts/health_check.sh
      timeout: 300

Why this structure? Because deploying software is more than just copying files. You need to stop the old service, maybe set up a new virtual environment, start the new one, and then—crucially—verify it actually works. The ValidateService hook is your last line of defense before CodeDeploy calls the server “done” and moves on. If this script exits with a non-zero code, CodeDeploy fails the deployment on that instance. Use it.

Now, the pitfalls of in-place deployments are legendary. You’re messing with a live, production system. What if the new dependencies break something the old ones relied on? What if your health check isn’t comprehensive enough and you end up with a zombie process serving errors? It’s fraught. This is why we have the superior alternative.

Blue/Green Deployments: The Only Sane Choice for EC2

Blue/Green is the deployment strategy you wish you always had. Instead of updating in-place, CodeDeploy provisions a brand-new, identical set of EC2 instances (the “Green” environment) from your latest AMI, deploys the new application version to them, and then shifts traffic over from the old set (the “Blue” environment). If anything goes wrong, shifting traffic back is a single, fast operation. It’s a night-and-day difference in risk.

The beauty is in the traffic control. For EC2, this isn’t handled by CodeDeploy itself but by integrating with Elastic Load Balancing (ELB) or Amazon’s Route 53. You tell CodeDeploy which load balancer to use, and it handles the registration and deregistration of instances automatically during the cut-over.

The appspec.yml is largely the same, but the context is different. You’re scripting the setup of a new environment, not altering a live one. Your scripts can be more optimistic.

The catch? Cost. You need to have the spare capacity (or an Auto Scaling group that can scale out) to run two environments simultaneously, albeit briefly. It’s a small price to pay for not having to explain a production outage. For anything with real traffic, this isn’t an option; it’s a necessity.

Lambda: Where It Gets Almost Too Easy

Now, for Lambda, CodeDeploy shifts gears entirely. There are no servers, no appspec.yml files to write, and no scripts to run. You’re not deploying to an OS; you’re deploying a function. CodeDeploy’s job here is to manage the traffic shift between two versions of your Lambda function alias (usually $LATEST is a terrible choice for this, by the way—use a named alias like prod).

The deployment strategies are correspondingly more elegant. You can do a canary deployment, which routes a small percentage of traffic to the new version for a while before completing the shift, or a linear deployment that shifts traffic in equal increments. This is where you truly appreciate the power of serverless.

Here’s the kicker: you configure this right in your CodeBuild buildspec or wherever you’re packaging your function. You need to tell the deployment which alias to update and what strategy to use.

version: 0.2
phases:
  build:
    commands:
      - zip -r my-function.zip lambda_function.py
  post_build:
    commands:
      - aws lambda update-function-code --function-name MyFunction --zip-file fileb://my-function.zip
      - aws deploy create-deployment \
          --application-name MyLambdaApp \
          --deployment-group-name MyLambdaDG \
          --revision '{
            "revisionType": "AppSpecContent",
            "appSpecContent": {
              "content": "{
                \"version\": \"0.0\",
                \"Resources\": [{
                  \"TargetService\": {
                    \"Type\": \"AWS::Lambda::Function\",
                    \"Properties\": {
                      \"Name\": \"MyFunction\",
                      \"Alias\": \"prod\",
                      \"CurrentVersion\": \"1\",
                      \"TargetVersion\": \"2\"
                    }
                  }
                }]
              }"
            }
          }'

(Note: In reality, you’d likely use CloudFormation or SAM for this, but this shows the underlying mechanics.)

The common pitfall here? Permissions. The CodeDeploy service role needs permissions to invoke your Lambda functions and to update their aliases. If you get an “access denied” error mid-deployment, that’s why. It’s always IAM. Always.

So, the rule of thumb: use Blue/Green for EC2. Always. The extra complexity is a rounding error compared to the operational risk of in-place. For Lambda, let CodeDeploy handle the traffic shifting for you—it’s one of the simplest ways to get safe, incremental deployments without building a complex pipeline yourself.